commit b5523acad0b7b7d52f64ec165b7c8f74ca2098e5 Author: Katajisto Date: Sat Apr 26 10:51:41 2025 +0300 work diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e245577 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4fa9b32 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# JAI Sokol WASM example + +This is a small project to demo how to use Jai + Sokol and output to WASM. + +Notes: +- Jai only supports WASM64 at the moment, so we need the [Memory64](https://github.com/WebAssembly/memory64/blob/main/proposals/memory64/Overview.md) extension for now. It's available as a feature flag in [some browsers](https://webassembly.org/features/#table-row-memory64). +- I'm (unfortunately) using [emscripten](https://emscripten.org) to compile the final wasm file so everything is still clunky. +- Sokol-jai: https://github.com/colinbellino/sokol-jai + +## How to build? +- Run `jai first.jai`, which should output in the `dist/` folder. +- Serve the `dist/` folder with any server you want (any tool that can app serve static assets should work). +- Open in a browser. (http://127.0.0.1:8080 for example) \ No newline at end of file diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..7b3550f --- /dev/null +++ b/dist/index.html @@ -0,0 +1,79 @@ + + + + +${name} + + + +
${name} + home +
+ + + + + diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..73abf8b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,4927 @@ +// include: shell.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 = typeof window == 'object'; +var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != 'undefined'; +// N.b. Electron.js environment is simultaneously a NODE-environment, but +// also a web environment. +var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string' && process.type != 'renderer'; +var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + +if (ENVIRONMENT_IS_NODE) { + +} + +// --pre-jses are emitted after the Module integration code, so that they can +// refer to Module (if they choose; they can also define Module) + + +// Sometimes an existing Module object exists with properties +// meant to overwrite the default module functionality. Here +// we collect those properties and reapply _after_ we configure +// the current environment's defaults to avoid having to be so +// defensive during initialization. +var moduleOverrides = {...Module}; + +var arguments_ = []; +var thisProgram = './this.program'; +var quit_ = (status, toThrow) => { + throw toThrow; +}; + +// `/` 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_NODE) { + if (typeof process == 'undefined' || !process.release || process.release.name !== 'node') 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?)'); + + var nodeVersion = process.versions.node; + var numericVersion = nodeVersion.split('.').slice(0, 3); + numericVersion = (numericVersion[0] * 10000) + (numericVersion[1] * 100) + (numericVersion[2].split('-')[0] * 1); + var minVersion = 230000; + if (numericVersion < 230000) { + throw new Error('This emscripten-generated code requires node v23.0.0 (detected v' + nodeVersion + ')'); + } + + // These modules will usually be used on Node.js. Load them eagerly to avoid + // the complexity of lazy-loading. + var fs = require('fs'); + var nodePath = require('path'); + + scriptDirectory = __dirname + '/'; + +// include: node_shell_read.js +readBinary = (filename) => { + // We need to re-wrap `file://` strings to URLs. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename); + assert(Buffer.isBuffer(ret)); + return ret; +}; + +readAsync = async (filename, binary = true) => { + // See the comment in the `readBinary` function. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); + assert(binary ? Buffer.isBuffer(ret) : typeof ret == 'string'); + return ret; +}; +// end include: node_shell_read.js + if (!Module['thisProgram'] && process.argv.length > 1) { + thisProgram = process.argv[1].replace(/\\/g, '/'); + } + + arguments_ = process.argv.slice(2); + + if (typeof module != 'undefined') { + module['exports'] = Module; + } + + quit_ = (status, toThrow) => { + process.exitCode = status; + throw toThrow; + }; + +} else +if (ENVIRONMENT_IS_SHELL) { + + if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof WorkerGlobalScope != 'undefined') 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?)'); + +} 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) { + if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled + scriptDirectory = self.location.href; + } else if (typeof document != 'undefined' && document.currentScript) { // web + scriptDirectory = document.currentScript.src; + } + // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them. + // otherwise, slice off the final part of the url to find the script directory. + // if scriptDirectory does not contain a slash, lastIndexOf will return -1, + // and scriptDirectory will correctly be replaced with an empty string. + // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #), + // they are removed because they could contain a slash. + if (scriptDirectory.startsWith('blob:')) { + scriptDirectory = ''; + } else { + scriptDirectory = scriptDirectory.slice(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1); + } + + if (!(typeof window == 'object' || typeof WorkerGlobalScope != 'undefined')) 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 +if (ENVIRONMENT_IS_WORKER) { + readBinary = (url) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.responseType = 'arraybuffer'; + xhr.send(null); + return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); + }; + } + + readAsync = async (url) => { + // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. + // See https://github.com/github/fetch/pull/92#issuecomment-140665932 + // Cordova or Electron apps are typically loaded from a file:// url. + // So use XHR on webview if URL is a file URL. + if (isFileURI(url)) { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = () => { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 + resolve(xhr.response); + return; + } + reject(xhr.status); + }; + xhr.onerror = reject; + xhr.send(null); + }); + } + 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 = Module['print'] || console.log.bind(console); +var err = Module['printErr'] || console.error.bind(console); + +// Merge back in the overrides +Object.assign(Module, moduleOverrides); +// Free the object hierarchy contained in the overrides, this lets the GC +// reclaim data used. +moduleOverrides = null; +checkIncomingModuleAPI(); + +// Emit code to handle expected values on the Module object. This applies Module.x +// to the proper local x. This has two benefits: first, we only emit it if it is +// expected to arrive, and second, by using a local everywhere else that can be +// minified. + +if (Module['arguments']) arguments_ = Module['arguments'];legacyModuleProp('arguments', 'arguments_'); + +if (Module['thisProgram']) thisProgram = Module['thisProgram'];legacyModuleProp('thisProgram', 'thisProgram'); + +// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message +// 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'); +legacyModuleProp('asm', 'wasmExports'); +legacyModuleProp('readAsync', 'readAsync'); +legacyModuleProp('readBinary', 'readBinary'); +legacyModuleProp('setWindowTitle', 'setWindowTitle'); +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'; + +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 = Module['wasmBinary'];legacyModuleProp('wasmBinary', 'wasmBinary'); + +if (typeof WebAssembly != 'object') { + err('no native wasm support detected'); +} + +// Wasm globals + +var wasmMemory; + +//======================================== +// 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 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'); +} + +// Memory management + +var HEAP, +/** @type {!Int8Array} */ + HEAP8, +/** @type {!Uint8Array} */ + HEAPU8, +/** @type {!Int16Array} */ + HEAP16, +/** @type {!Uint16Array} */ + HEAPU16, +/** @type {!Int32Array} */ + HEAP32, +/** @type {!Uint32Array} */ + HEAPU32, +/** @type {!Float32Array} */ + HEAPF32, +/* BigInt64Array type is not correctly defined in closure +/** not-@type {!BigInt64Array} */ + HEAP64, +/* BigUint64Array type is not correctly defined in closure +/** not-t@type {!BigUint64Array} */ + HEAPU64, +/** @type {!Float64Array} */ + HEAPF64; + +var runtimeInitialized = false; + +/** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ +var isFileURI = (filename) => filename.startsWith('file://'); + +// include: runtime_shared.js +// include: runtime_stack_check.js +// 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)/4)] = 0x02135467; + HEAPU32[(((max)+(4))/4)] = 0x89BACDFE; + // Also test the global address 0 for integrity. + HEAPU32[((0)/4)] = 1668509029; +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var cookie1 = HEAPU32[((max)/4)]; + var cookie2 = HEAPU32[(((max)+(4))/4)]; + if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)/4)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} +// end include: runtime_stack_check.js +// include: runtime_exceptions.js +// end include: runtime_exceptions.js +// include: runtime_debug.js +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; +})(); + +if (Module['ENVIRONMENT']) { + throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); +} + +function legacyModuleProp(prop, newName, incoming=true) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + get() { + let extra = incoming ? ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)' : ''; + abort(`\`Module.${prop}\` has been replaced by \`${newName}\`` + extra); + + } + }); + } +} + +function consumedModuleProp(prop) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + set() { + abort(`Attempt to set \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`); + + } + }); + } +} + +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_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a 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. + */ +function hookGlobalSymbolAccess(sym, func) { + if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is not 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); + } + }); + } +} + +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); +} +// end include: runtime_debug.js +// include: memoryprofiler.js +// end include: memoryprofiler.js + + +function updateMemoryViews() { + var b = wasmMemory.buffer; + Module['HEAP8'] = HEAP8 = new Int8Array(b); + Module['HEAP16'] = HEAP16 = new Int16Array(b); + Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); + Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); + Module['HEAP32'] = HEAP32 = new Int32Array(b); + Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); + Module['HEAPF32'] = HEAPF32 = new Float32Array(b); + Module['HEAPF64'] = HEAPF64 = new Float64Array(b); + Module['HEAP64'] = HEAP64 = new BigInt64Array(b); + Module['HEAPU64'] = HEAPU64 = new BigUint64Array(b); +} + +// end include: runtime_shared.js +assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + +assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, + 'JS engine does not provide full typed array support'); + +// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY +assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); +assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + +function preRun() { + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + consumedModuleProp('preRun'); + callRuntimeCallbacks(onPreRuns); +} + +function initRuntime() { + assert(!runtimeInitialized); + runtimeInitialized = true; + + checkStackCookie(); + + + + wasmExports['__wasm_call_ctors'](); + + +} + +function preMain() { + checkStackCookie(); + +} + +function postRun() { + checkStackCookie(); + + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + consumedModuleProp('postRun'); + + callRuntimeCallbacks(onPostRuns); +} + +// A counter of dependencies for calling run(). If we need to +// do asynchronous work before running, increment this and +// decrement it. Incrementing must happen in a place like +// Module.preRun (used by emcc to add file preloading). +// Note that you can add dependencies in preRun, even though +// it happens right before run - run will be postponed until +// the dependencies are met. +var runDependencies = 0; +var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled +var runDependencyTracking = {}; +var runDependencyWatcher = null; + +function getUniqueRunDependency(id) { + var orig = id; + while (1) { + if (!runDependencyTracking[id]) return id; + id = orig + Math.random(); + } +} + +function addRunDependency(id) { + runDependencies++; + + Module['monitorRunDependencies']?.(runDependencies); + + if (id) { + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && typeof setInterval != 'undefined') { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(() => { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err(`dependency: ${dep}`); + } + if (shown) { + err('(end of list)'); + } + }, 10000); + } + } else { + err('warning: run dependency added without ID'); + } +} + +function removeRunDependency(id) { + runDependencies--; + + Module['monitorRunDependencies']?.(runDependencies); + + if (id) { + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; + } else { + err('warning: run dependency removed without ID'); + } + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled + } + } +} + +/** @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 +var FS = { + error() { + 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'); + }, + init() { FS.error() }, + createDataFile() { FS.error() }, + createPreloadedFile() { FS.error() }, + createLazyFile() { FS.error() }, + open() { FS.error() }, + mkdev() { FS.error() }, + registerDevice() { FS.error() }, + analyzePath() { FS.error() }, + + ErrnoError() { FS.error() }, +}; +Module['FS_createDataFile'] = FS.createDataFile; +Module['FS_createPreloadedFile'] = FS.createPreloadedFile; + +function createExportWrapper(name, nargs) { + return (...args) => { + assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); + var f = wasmExports[name]; + assert(f, `exported native function \`${name}\` not found`); + // 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 f(...args); + }; +} + +var wasmBinaryFile; + +function findWasmBinary() { + return locateFile('index.wasm'); +} + +function getBinarySync(file) { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + 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(wasmBinaryFile)) { + err(`warning: Loading from a file URI (${wasmBinaryFile}) 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 && typeof WebAssembly.instantiateStreaming == 'function' + // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. + && !isFileURI(binaryFile) + // Avoid instantiateStreaming() on Node.js environment for now, as while + // Node.js v18.1.0 implements it, it does not have a full fetch() + // implementation yet. + // + // Reference: + // https://github.com/emscripten-core/emscripten/pull/16917 + && !ENVIRONMENT_IS_NODE + ) { + 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 + return { + 'env': wasmImports, + 'wasi_snapshot_preview1': wasmImports, + } +} + +// 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 + /** @param {WebAssembly.Module=} module*/ + function receiveInstance(instance, module) { + wasmExports = instance.exports; + + wasmExports = applySignatureConversions(wasmExports); + + + + wasmMemory = wasmExports['memory']; + + assert(wasmMemory, 'memory not found in wasm exports'); + updateMemoryViews(); + + wasmTable = wasmExports['__indirect_function_table']; + + assert(wasmTable, 'table not found in wasm exports'); + + removeRunDependency('wasm-instantiate'); + return wasmExports; + } + // wait for the pthread pool (if any) + addRunDependency('wasm-instantiate'); + + // 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. + if (Module['instantiateWasm']) { + return new Promise((resolve, reject) => { + try { + Module['instantiateWasm'](info, (mod, inst) => { + receiveInstance(mod, inst); + resolve(mod.exports); + }); + } catch(e) { + err(`Module.instantiateWasm callback failed with error: ${e}`); + reject(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; + } + } + + var callRuntimeCallbacks = (callbacks) => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }; + var onPostRuns = []; + var addOnPostRun = (cb) => onPostRuns.unshift(cb); + + var onPreRuns = []; + var addOnPreRun = (cb) => onPreRuns.unshift(cb); + + + + /** + * @param {number} ptr + * @param {string} type + */ + function getValue(ptr, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': return HEAP8[ptr]; + case 'i8': return HEAP8[ptr]; + case 'i16': return HEAP16[((ptr)/2)]; + case 'i32': return HEAP32[((ptr)/4)]; + case 'i64': return HEAP64[((ptr)/8)]; + case 'float': return HEAPF32[((ptr)/4)]; + case 'double': return HEAPF64[((ptr)/8)]; + case '*': return Number(HEAPU64[((ptr)/8)]); + default: abort(`invalid type for getValue: ${type}`); + } + } + + var noExitRuntime = Module['noExitRuntime'] || true; + + var ptrToString = (ptr) => { + assert(typeof ptr === 'number'); + return '0x' + ptr.toString(16).padStart(8, '0'); + }; + + + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ + function setValue(ptr, value, type = 'i8') { + if (type.endsWith('*')) type = '*'; + switch (type) { + case 'i1': HEAP8[ptr] = value; break; + case 'i8': HEAP8[ptr] = value; break; + case 'i16': HEAP16[((ptr)/2)] = value; break; + case 'i32': HEAP32[((ptr)/4)] = value; break; + case 'i64': HEAP64[((ptr)/8)] = BigInt(value); break; + case 'float': HEAPF32[((ptr)/4)] = value; break; + case 'double': HEAPF64[((ptr)/8)] = value; break; + case '*': HEAPU64[((ptr)/8)] = BigInt(value); break; + default: abort(`invalid type for setValue: ${type}`); + } + } + + 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; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + }; + + var INT53_MAX = 9007199254740992; + + var INT53_MIN = -9007199254740992; + var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); + + var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; + + /** + * 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 + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. Also, use the length info to avoid running tiny + // strings through TextDecoder, since .subarray() allocates garbage. + // (As a tiny code save trick, compare endPtr against endIdx using a negation, + // so that undefined/NaN means Infinity) + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + // If building with TextDecoder, we have already computed the string length + // above, so test loop end condition against that + 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; + }; + + + /** + * 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 (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + function UTF8ToString(ptr, maxBytesToRead) { + ptr = bigintToI53Checked(ptr); + + + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; + ; + } + function ___assert_fail(condition, filename, line, func) { + condition = bigintToI53Checked(condition); + filename = bigintToI53Checked(filename); + func = bigintToI53Checked(func); + + return 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_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 (!MainLoop.running) { + + MainLoop.running = true; + } + 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 + }; + MainLoop.method = 'timeout'; + } else if (mode == 1) { + MainLoop.scheduler = function MainLoop_scheduler_rAF() { + MainLoop.requestAnimationFrame(MainLoop.runner); + }; + MainLoop.method = 'rAF'; + } else if (mode == 2) { + if (typeof MainLoop.setImmediate == 'undefined') { + if (typeof setImmediate == 'undefined') { + // 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) => { + // When called in current thread or Worker, the main loop ID is structured slightly different to accommodate for --proxy-to-worker runtime listening to Worker events, + // so check for both cases. + if (event.data === emscriptenMainLoopMessageId || event.data.target === 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) { + Module['setImmediates'] ??= []; + Module['setImmediates'].push(func); + postMessage({target: emscriptenMainLoopMessageId}); // In --proxy-to-worker, route the message via proxyClient.js + } else postMessage(emscriptenMainLoopMessageId, "*"); // On the main thread, can just send the message to itself. + }); + } else { + MainLoop.setImmediate = setImmediate; + } + } + MainLoop.scheduler = function MainLoop_scheduler_setImmediate() { + MainLoop.setImmediate(MainLoop.runner); + }; + MainLoop.method = 'immediate'; + } + return 0; + }; + + var _emscripten_get_now = () => performance.now(); + + + 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)); + }; + + + /** @suppress {duplicate } */ + /** @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: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.'); + 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 a + // later time). This member signifies that the current runner has not + // yet been started so that we can call runtimeKeepalivePush when it + // gets it timing set for the first time. + MainLoop.running = false; + 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 (MainLoop.method === 'timeout' && 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.method = ''; // just warn once per call to set main loop + } + + 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 { + func(); + maybeExit(); + } catch (e) { + handleException(e); + } + }; + + var MainLoop = { + running:false, + scheduler:null, + method:"", + currentlyRunningMainloop:0, + func:null, + arg:0, + timingMode:0, + timingValue:0, + currentFrameNumber:0, + queue:[], + preMainLoop:[], + postMainLoop:[], + pause() { + 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() { + Module['preMainLoop'] && MainLoop.preMainLoop.push(Module['preMainLoop']); + Module['postMainLoop'] && MainLoop.postMainLoop.push(Module['postMainLoop']); + }, + 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 === 0) { + 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 (typeof requestAnimationFrame == 'function') { + requestAnimationFrame(func); + return; + } + var RAF = MainLoop.fakeRequestAnimationFrame; + RAF(func); + }, + }; + var _emscripten_cancel_main_loop = () => { + MainLoop.pause(); + MainLoop.func = null; + }; + + var _emscripten_get_device_pixel_ratio = () => { + return (typeof devicePixelRatio == 'number' && devicePixelRatio) || 1.0; + }; + + 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, typeof document != 'undefined' ? document : 0, typeof window != 'undefined' ? window : 0]; + var findEventTarget = (target) => { + target = maybeCStringToJsString(target); + var domElement = specialHTMLTargets[target] || (typeof document != 'undefined' ? document.querySelector(target) : null); + return domElement; + }; + + var getBoundingClientRect = (e) => specialHTMLTargets.indexOf(e) < 0 ? e.getBoundingClientRect() : {'left':0,'top':0}; + + function _emscripten_get_element_css_size(target, width, height) { + target = bigintToI53Checked(target); + width = bigintToI53Checked(width); + height = bigintToI53Checked(height); + + + target = findEventTarget(target); + if (!target) return -4; + + var rect = getBoundingClientRect(target); + HEAPF64[((width)/8)] = rect.width; + HEAPF64[((height)/8)] = rect.height; + + return 0; + ; + } + + + var _emscripten_performance_now = () => performance.now(); + + + var wasmTableMirror = []; + + /** @type {WebAssembly.Table} */ + var wasmTable; + var getWasmTableEntry = (funcPtr) => { + // Function pointers should show up as numbers, even under wasm64, but + // we still have some places where bigint values can flow here. + // https://github.com/emscripten-core/emscripten/issues/18200 + funcPtr = Number(funcPtr); + var func = wasmTableMirror[funcPtr]; + if (!func) { + /** @suppress {checkTypes} */ + wasmTableMirror[funcPtr] = func = wasmTable.get(BigInt(funcPtr)); + } + /** @suppress {checkTypes} */ + assert(wasmTable.get(BigInt(funcPtr)) == func, 'JavaScript-side Wasm function table mirror is out of date!'); + return func; + }; + var _emscripten_request_animation_frame_loop = function(cb, userData) { + cb = bigintToI53Checked(cb); + userData = bigintToI53Checked(userData); + + + function tick(timeStamp) { + if (((a1, a2) => getWasmTableEntry(cb).call(null, a1, BigInt(a2)))(timeStamp, userData)) { + requestAnimationFrame(tick); + } + } + return requestAnimationFrame(tick); + ; + }; + + var abortOnCannotGrowMemory = (requestedSize) => { + abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`); + }; + + function _emscripten_resize_heap(requestedSize) { + requestedSize = bigintToI53Checked(requestedSize); + + + var oldSize = HEAPU8.length; + abortOnCannotGrowMemory(requestedSize); + ; + } + + var onExits = []; + var addOnExit = (cb) => onExits.unshift(cb); + var JSEvents = { + memcpy(target, src, size) { + HEAP8.set(HEAP8.subarray(src, src + size), target); + }, + 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 in arrA) { + 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() { + 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 + // At the time of writing, Firefox does not support this API: https://bugzilla.mozilla.org/show_bug.cgi?id=1791079 + 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; + }, + getNodeNameForTarget(target) { + if (!target) return ''; + if (target == window) return '#window'; + if (target == screen) return '#screen'; + return target?.nodeName || ''; + }, + fullscreenEnabled() { + return document.fullscreenEnabled + ; + }, + }; + + + + 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) { + // 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 + // 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.charCodeAt(i); // possibly a lead surrogate + if (u >= 0xD800 && u <= 0xDFFF) { + var u1 = str.charCodeAt(++i); + u = 0x10000 + ((u & 0x3FF) << 10) | (u1 & 0x3FF); + } + 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); + } + } + // Null-terminate the pointer to the buffer. + heap[outIdx] = 0; + return outIdx - startIdx; + }; + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { + assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); + return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + }; + + var registerFocusEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + JSEvents.focusEvent ||= _malloc(256); + + var focusEventHandlerFunc = (e = event) => { + var nodeName = JSEvents.getNodeNameForTarget(e.target); + var id = e.target.id ? e.target.id : ''; + + var focusEvent = JSEvents.focusEvent; + stringToUTF8(nodeName, focusEvent + 0, 128); + stringToUTF8(id, focusEvent + 128, 128); + + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, focusEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: focusEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + function _emscripten_set_blur_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerFocusEventCallback(target, userData, useCapture, callbackfunc, 12, "blur", targetThread); + } + + var findCanvasEventTarget = findEventTarget; + + function _emscripten_set_canvas_element_size(target, width, height) { + target = bigintToI53Checked(target); + + + var canvas = findCanvasEventTarget(target); + if (!canvas) return -4; + canvas.width = width; + canvas.height = height; + return 0; + ; + } + + + function _emscripten_set_focus_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerFocusEventCallback(target, userData, useCapture, callbackfunc, 13, "focus", targetThread); + } + + + + + + var registerKeyEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + JSEvents.keyEvent ||= _malloc(160); + + var keyEventHandlerFunc = (e) => { + assert(e); + + var keyEventData = JSEvents.keyEvent; + HEAPF64[((keyEventData)/8)] = e.timeStamp; + + var idx = ((keyEventData)/4); + + 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 (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, keyEventData, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: keyEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + function _emscripten_set_keydown_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerKeyEventCallback(target, userData, useCapture, callbackfunc, 2, "keydown", targetThread); + } + + + function _emscripten_set_keypress_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerKeyEventCallback(target, userData, useCapture, callbackfunc, 1, "keypress", targetThread); + } + + + function _emscripten_set_keyup_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerKeyEventCallback(target, userData, useCapture, callbackfunc, 3, "keyup", targetThread); + } + + + + function _emscripten_set_main_loop(func, fps, simulateInfiniteLoop) { + func = bigintToI53Checked(func); + + + var iterFunc = getWasmTableEntry(func); + setMainLoop(iterFunc, fps, simulateInfiniteLoop); + ; + } + + + var fillMouseEventData = (eventStruct, e, target) => { + assert(eventStruct % 4 == 0); + HEAPF64[((eventStruct)/8)] = e.timeStamp; + var idx = ((eventStruct)/4); + 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) => { + JSEvents.mouseEvent ||= _malloc(64); + target = findEventTarget(target); + + var mouseEventHandlerFunc = (e = event) => { + // TODO: Make this access thread safe, or this could update live while app is reading it. + fillMouseEventData(JSEvents.mouseEvent, e, target); + + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(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, + callbackfunc, + handlerFunc: mouseEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + function _emscripten_set_mousedown_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerMouseEventCallback(target, userData, useCapture, callbackfunc, 5, "mousedown", targetThread); + } + + + function _emscripten_set_mouseenter_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerMouseEventCallback(target, userData, useCapture, callbackfunc, 33, "mouseenter", targetThread); + } + + + function _emscripten_set_mouseleave_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerMouseEventCallback(target, userData, useCapture, callbackfunc, 34, "mouseleave", targetThread); + } + + + function _emscripten_set_mousemove_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerMouseEventCallback(target, userData, useCapture, callbackfunc, 8, "mousemove", targetThread); + } + + + function _emscripten_set_mouseup_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerMouseEventCallback(target, userData, useCapture, callbackfunc, 6, "mouseup", targetThread); + } + + + + var fillPointerlockChangeEventData = (eventStruct) => { + var pointerLockElement = document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement || document.msPointerLockElement; + var isPointerlocked = !!pointerLockElement; + // Assigning a boolean to HEAP32 with expected type coercion. + /** @suppress{checkTypes} */ + HEAP8[eventStruct] = isPointerlocked; + var nodeName = JSEvents.getNodeNameForTarget(pointerLockElement); + var id = pointerLockElement?.id || ''; + stringToUTF8(nodeName, eventStruct + 1, 128); + stringToUTF8(id, eventStruct + 129, 128); + }; + + + var registerPointerlockChangeEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + JSEvents.pointerlockChangeEvent ||= _malloc(257); + + var pointerlockChangeEventHandlerFunc = (e = event) => { + var pointerlockChangeEvent = JSEvents.pointerlockChangeEvent; + fillPointerlockChangeEventData(pointerlockChangeEvent); + + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, pointerlockChangeEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: pointerlockChangeEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + + /** @suppress {missingProperties} */ + function _emscripten_set_pointerlockchange_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + + // TODO: Currently not supported in pthreads or in --proxy-to-worker mode. (In pthreads mode, document object is not defined) + if (!document || !document.body || (!document.body.requestPointerLock && !document.body.mozRequestPointerLock && !document.body.webkitRequestPointerLock && !document.body.msRequestPointerLock)) { + return -1; + } + + target = findEventTarget(target); + if (!target) return -4; + registerPointerlockChangeEventCallback(target, userData, useCapture, callbackfunc, 20, "mozpointerlockchange", targetThread); + registerPointerlockChangeEventCallback(target, userData, useCapture, callbackfunc, 20, "webkitpointerlockchange", targetThread); + registerPointerlockChangeEventCallback(target, userData, useCapture, callbackfunc, 20, "mspointerlockchange", targetThread); + return registerPointerlockChangeEventCallback(target, userData, useCapture, callbackfunc, 20, "pointerlockchange", targetThread); + ; + } + + + var registerPointerlockErrorEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + + var pointerlockErrorEventHandlerFunc = (e = event) => { + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, 0, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: pointerlockErrorEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + + /** @suppress {missingProperties} */ + function _emscripten_set_pointerlockerror_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + + // TODO: Currently not supported in pthreads or in --proxy-to-worker mode. (In pthreads mode, document object is not defined) + if (!document || !document.body.requestPointerLock && !document.body.mozRequestPointerLock && !document.body.webkitRequestPointerLock && !document.body.msRequestPointerLock) { + return -1; + } + + target = findEventTarget(target); + + if (!target) return -4; + registerPointerlockErrorEventCallback(target, userData, useCapture, callbackfunc, 38, "mozpointerlockerror", targetThread); + registerPointerlockErrorEventCallback(target, userData, useCapture, callbackfunc, 38, "webkitpointerlockerror", targetThread); + registerPointerlockErrorEventCallback(target, userData, useCapture, callbackfunc, 38, "mspointerlockerror", targetThread); + return registerPointerlockErrorEventCallback(target, userData, useCapture, callbackfunc, 38, "pointerlockerror", targetThread); + ; + } + + + + + var registerUiEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + JSEvents.uiEvent ||= _malloc(36); + + target = findEventTarget(target); + + var uiEventHandlerFunc = (e = event) => { + if (e.target != target) { + // Never take ui events such as scroll via a 'bubbled' route, but always from the direct element that + // was targeted. Otherwise e.g. if app logs a message in response to a page scroll, the Emscripten log + // message box could cause to scroll, generating a new (bubbled) scroll message, causing a new log print, + // causing a new scroll, etc.. + return; + } + var b = document.body; // Take document.body to a variable, Closure compiler does not outline access to it on its own. + if (!b) { + // During a page unload 'body' can be null, with "Cannot read property 'clientWidth' of null" being thrown + return; + } + var uiEvent = JSEvents.uiEvent; + HEAP32[((uiEvent)/4)] = 0; // always zero for resize and scroll + HEAP32[(((uiEvent)+(4))/4)] = b.clientWidth; + HEAP32[(((uiEvent)+(8))/4)] = b.clientHeight; + HEAP32[(((uiEvent)+(12))/4)] = innerWidth; + HEAP32[(((uiEvent)+(16))/4)] = innerHeight; + HEAP32[(((uiEvent)+(20))/4)] = outerWidth; + HEAP32[(((uiEvent)+(24))/4)] = outerHeight; + HEAP32[(((uiEvent)+(28))/4)] = pageXOffset | 0; // scroll offsets are float + HEAP32[(((uiEvent)+(32))/4)] = pageYOffset | 0; + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, uiEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + eventTypeString, + callbackfunc, + handlerFunc: uiEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + function _emscripten_set_resize_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerUiEventCallback(target, userData, useCapture, callbackfunc, 10, "resize", targetThread); + } + + + + + + var registerTouchEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + JSEvents.touchEvent ||= _malloc(1552); + + target = findEventTarget(target); + + var touchEventHandlerFunc = (e) => { + assert(e); + var t, touches = {}, et = e.touches; + // To ease marshalling different kinds of touches that browser reports (all touches are listed in e.touches, + // only changed touches in e.changedTouches, and touches on target at a.targetTouches), mark a boolean in + // each Touch object so that we can later loop only once over all touches we see to marshall over to Wasm. + + for (let t of et) { + // Browser might recycle the generated Touch objects between each frame (Firefox on Android), so reset any + // changed/target states we may have set from previous frame. + t.isChanged = t.onTarget = 0; + touches[t.identifier] = t; + } + // Mark which touches are part of the changedTouches list. + for (let t of e.changedTouches) { + t.isChanged = 1; + touches[t.identifier] = t; + } + // Mark which touches are part of the targetTouches list. + for (let t of e.targetTouches) { + touches[t.identifier].onTarget = 1; + } + + var touchEvent = JSEvents.touchEvent; + HEAPF64[((touchEvent)/8)] = e.timeStamp; + HEAP8[touchEvent + 12] = e.ctrlKey; + HEAP8[touchEvent + 13] = e.shiftKey; + HEAP8[touchEvent + 14] = e.altKey; + HEAP8[touchEvent + 15] = e.metaKey; + var idx = touchEvent + 16; + var targetRect = getBoundingClientRect(target); + var numTouches = 0; + for (let t of Object.values(touches)) { + var idx32 = ((idx)/4); // Pre-shift the ptr to index to HEAP32 to save code size + HEAP32[idx32 + 0] = t.identifier; + HEAP32[idx32 + 1] = t.screenX; + HEAP32[idx32 + 2] = t.screenY; + HEAP32[idx32 + 3] = t.clientX; + HEAP32[idx32 + 4] = t.clientY; + HEAP32[idx32 + 5] = t.pageX; + HEAP32[idx32 + 6] = t.pageY; + HEAP8[idx + 28] = t.isChanged; + HEAP8[idx + 29] = t.onTarget; + HEAP32[idx32 + 8] = t.clientX - (targetRect.left | 0); + HEAP32[idx32 + 9] = t.clientY - (targetRect.top | 0); + + idx += 48; + + if (++numTouches > 31) { + break; + } + } + HEAP32[(((touchEvent)+(8))/4)] = numTouches; + + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, touchEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + allowsDeferredCalls: eventTypeString == 'touchstart' || eventTypeString == 'touchend', + eventTypeString, + callbackfunc, + handlerFunc: touchEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + function _emscripten_set_touchcancel_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerTouchEventCallback(target, userData, useCapture, callbackfunc, 25, "touchcancel", targetThread); + } + + + function _emscripten_set_touchend_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerTouchEventCallback(target, userData, useCapture, callbackfunc, 23, "touchend", targetThread); + } + + + function _emscripten_set_touchmove_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerTouchEventCallback(target, userData, useCapture, callbackfunc, 24, "touchmove", targetThread); + } + + + function _emscripten_set_touchstart_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + return registerTouchEventCallback(target, userData, useCapture, callbackfunc, 22, "touchstart", targetThread); + } + + + + var GLctx; + + var webgl_enable_ANGLE_instanced_arrays = (ctx) => { + // Extension available in WebGL 1 from Firefox 26 and Google Chrome 30 onwards. Core feature in WebGL 2. + var ext = ctx.getExtension('ANGLE_instanced_arrays'); + // Because this extension is a core function in WebGL 2, assign the extension entry points in place of + // where the core functions will reside in WebGL 2. This way the calling code can call these without + // having to dynamically branch depending if running against WebGL 1 or WebGL 2. + if (ext) { + ctx['vertexAttribDivisor'] = (index, divisor) => ext['vertexAttribDivisorANGLE'](index, divisor); + ctx['drawArraysInstanced'] = (mode, first, count, primcount) => ext['drawArraysInstancedANGLE'](mode, first, count, primcount); + ctx['drawElementsInstanced'] = (mode, count, type, indices, primcount) => ext['drawElementsInstancedANGLE'](mode, count, type, indices, primcount); + return 1; + } + }; + + var webgl_enable_OES_vertex_array_object = (ctx) => { + // Extension available in WebGL 1 from Firefox 25 and WebKit 536.28/desktop Safari 6.0.3 onwards. Core feature in WebGL 2. + var ext = ctx.getExtension('OES_vertex_array_object'); + if (ext) { + ctx['createVertexArray'] = () => ext['createVertexArrayOES'](); + ctx['deleteVertexArray'] = (vao) => ext['deleteVertexArrayOES'](vao); + ctx['bindVertexArray'] = (vao) => ext['bindVertexArrayOES'](vao); + ctx['isVertexArray'] = (vao) => ext['isVertexArrayOES'](vao); + return 1; + } + }; + + var webgl_enable_WEBGL_draw_buffers = (ctx) => { + // Extension available in WebGL 1 from Firefox 28 onwards. Core feature in WebGL 2. + var ext = ctx.getExtension('WEBGL_draw_buffers'); + if (ext) { + ctx['drawBuffers'] = (n, bufs) => ext['drawBuffersWEBGL'](n, bufs); + return 1; + } + }; + + var webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance = (ctx) => + // Closure is expected to be allowed to minify the '.dibvbi' property, so not accessing it quoted. + !!(ctx.dibvbi = ctx.getExtension('WEBGL_draw_instanced_base_vertex_base_instance')); + + var webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance = (ctx) => { + // Closure is expected to be allowed to minify the '.mdibvbi' property, so not accessing it quoted. + return !!(ctx.mdibvbi = ctx.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')); + }; + + var webgl_enable_EXT_polygon_offset_clamp = (ctx) => + !!(ctx.extPolygonOffsetClamp = ctx.getExtension('EXT_polygon_offset_clamp')); + + var webgl_enable_EXT_clip_control = (ctx) => + !!(ctx.extClipControl = ctx.getExtension('EXT_clip_control')); + + var webgl_enable_WEBGL_polygon_mode = (ctx) => + !!(ctx.webglPolygonMode = ctx.getExtension('WEBGL_polygon_mode')); + + var webgl_enable_WEBGL_multi_draw = (ctx) => + // Closure is expected to be allowed to minify the '.multiDrawWebgl' property, so not accessing it quoted. + !!(ctx.multiDrawWebgl = ctx.getExtension('WEBGL_multi_draw')); + + var getEmscriptenSupportedExtensions = (ctx) => { + // Restrict the list of advertised extensions to those that we actually + // support. + var supportedExtensions = [ + // WebGL 1 extensions + 'ANGLE_instanced_arrays', + 'EXT_blend_minmax', + 'EXT_disjoint_timer_query', + 'EXT_frag_depth', + 'EXT_shader_texture_lod', + 'EXT_sRGB', + 'OES_element_index_uint', + 'OES_fbo_render_mipmap', + 'OES_standard_derivatives', + 'OES_texture_float', + 'OES_texture_half_float', + 'OES_texture_half_float_linear', + 'OES_vertex_array_object', + 'WEBGL_color_buffer_float', + 'WEBGL_depth_texture', + 'WEBGL_draw_buffers', + // WebGL 2 extensions + 'EXT_color_buffer_float', + 'EXT_conservative_depth', + 'EXT_disjoint_timer_query_webgl2', + 'EXT_texture_norm16', + 'NV_shader_noperspective_interpolation', + 'WEBGL_clip_cull_distance', + // WebGL 1 and WebGL 2 extensions + 'EXT_clip_control', + 'EXT_color_buffer_half_float', + 'EXT_depth_clamp', + 'EXT_float_blend', + 'EXT_polygon_offset_clamp', + 'EXT_texture_compression_bptc', + 'EXT_texture_compression_rgtc', + 'EXT_texture_filter_anisotropic', + 'KHR_parallel_shader_compile', + 'OES_texture_float_linear', + 'WEBGL_blend_func_extended', + 'WEBGL_compressed_texture_astc', + 'WEBGL_compressed_texture_etc', + 'WEBGL_compressed_texture_etc1', + 'WEBGL_compressed_texture_s3tc', + 'WEBGL_compressed_texture_s3tc_srgb', + 'WEBGL_debug_renderer_info', + 'WEBGL_debug_shaders', + 'WEBGL_lose_context', + 'WEBGL_multi_draw', + 'WEBGL_polygon_mode' + ]; + // .getSupportedExtensions() can return null if context is lost, so coerce to empty array. + return (ctx.getSupportedExtensions() || []).filter(ext => supportedExtensions.includes(ext)); + }; + + + var GL = { + counter:1, + buffers:[], + programs:[], + framebuffers:[], + renderbuffers:[], + textures:[], + shaders:[], + vaos:[], + contexts:[], + offscreenCanvases:{ + }, + queries:[], + samplers:[], + transformFeedbacks:[], + syncs:[], + stringCache:{ + }, + stringiCache:{ + }, + unpackAlignment:4, + unpackRowLength:0, + recordError:(errorCode) => { + if (!GL.lastError) { + GL.lastError = errorCode; + } + }, + getNewId:(table) => { + var ret = GL.counter++; + for (var i = table.length; i < ret; i++) { + table[i] = null; + } + return ret; + }, + genObject:(n, buffers, createFunction, objectTable + ) => { + for (var i = 0; i < n; i++) { + var buffer = GLctx[createFunction](); + var id = buffer && GL.getNewId(objectTable); + if (buffer) { + buffer.name = id; + objectTable[id] = buffer; + } else { + GL.recordError(0x502 /* GL_INVALID_OPERATION */); + } + HEAP32[(((buffers)+(i*4))/4)] = id; + } + }, + getSource:(shader, count, string, length) => { + var source = ''; + for (var i = 0; i < count; ++i) { + var len = length ? Number(HEAPU64[(((length)+(i*8))/8)]) : undefined; + source += UTF8ToString(Number(HEAPU64[(((string)+(i*8))/8)]), len); + } + return source; + }, + createContext:(/** @type {HTMLCanvasElement} */ canvas, webGLContextAttributes) => { + + var ctx = + (webGLContextAttributes.majorVersion > 1) + ? + canvas.getContext("webgl2", webGLContextAttributes) + : + canvas.getContext("webgl", webGLContextAttributes); + + if (!ctx) return 0; + + var handle = GL.registerContext(ctx, webGLContextAttributes); + + return handle; + }, + registerContext:(ctx, webGLContextAttributes) => { + // without pthreads a context is just an integer ID + var handle = GL.getNewId(GL.contexts); + + var context = { + handle, + attributes: webGLContextAttributes, + version: webGLContextAttributes.majorVersion, + GLctx: ctx + }; + + // Store the created context object so that we can access the context + // given a canvas without having to pass the parameters again. + if (ctx.canvas) ctx.canvas.GLctxObject = context; + GL.contexts[handle] = context; + if (typeof webGLContextAttributes.enableExtensionsByDefault == 'undefined' || webGLContextAttributes.enableExtensionsByDefault) { + GL.initExtensions(context); + } + + return handle; + }, + makeContextCurrent:(contextHandle) => { + + // Active Emscripten GL layer context object. + GL.currentContext = GL.contexts[contextHandle]; + // Active WebGL context object. + Module['ctx'] = GLctx = GL.currentContext?.GLctx; + return !(contextHandle && !GLctx); + }, + getContext:(contextHandle) => { + return GL.contexts[contextHandle]; + }, + deleteContext:(contextHandle) => { + if (GL.currentContext === GL.contexts[contextHandle]) { + GL.currentContext = null; + } + if (typeof JSEvents == 'object') { + // Release all JS event handlers on the DOM element that the GL context is + // associated with since the context is now deleted. + JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas); + } + // Make sure the canvas object no longer refers to the context object so + // there are no GC surprises. + if (GL.contexts[contextHandle]?.GLctx.canvas) { + GL.contexts[contextHandle].GLctx.canvas.GLctxObject = undefined; + } + GL.contexts[contextHandle] = null; + }, + initExtensions:(context) => { + // If this function is called without a specific context object, init the + // extensions of the currently active context. + context ||= GL.currentContext; + + if (context.initExtensionsDone) return; + context.initExtensionsDone = true; + + var GLctx = context.GLctx; + + // Detect the presence of a few extensions manually, ction GL interop + // layer itself will need to know if they exist. + + // Extensions that are available in both WebGL 1 and WebGL 2 + webgl_enable_WEBGL_multi_draw(GLctx); + webgl_enable_EXT_polygon_offset_clamp(GLctx); + webgl_enable_EXT_clip_control(GLctx); + webgl_enable_WEBGL_polygon_mode(GLctx); + // Extensions that are only available in WebGL 1 (the calls will be no-ops + // if called on a WebGL 2 context active) + webgl_enable_ANGLE_instanced_arrays(GLctx); + webgl_enable_OES_vertex_array_object(GLctx); + webgl_enable_WEBGL_draw_buffers(GLctx); + // Extensions that are available from WebGL >= 2 (no-op if called on a WebGL 1 context active) + webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx); + webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx); + + // On WebGL 2, EXT_disjoint_timer_query is replaced with an alternative + // that's based on core APIs, and exposes only the queryCounterEXT() + // entrypoint. + if (context.version >= 2) { + GLctx.disjointTimerQueryExt = GLctx.getExtension("EXT_disjoint_timer_query_webgl2"); + } + + // However, Firefox exposes the WebGL 1 version on WebGL 2 as well and + // thus we look for the WebGL 1 version again if the WebGL 2 version + // isn't present. https://bugzilla.mozilla.org/show_bug.cgi?id=1328882 + if (context.version < 2 || !GLctx.disjointTimerQueryExt) + { + GLctx.disjointTimerQueryExt = GLctx.getExtension("EXT_disjoint_timer_query"); + } + + getEmscriptenSupportedExtensions(GLctx).forEach((ext) => { + // WEBGL_lose_context, WEBGL_debug_renderer_info and WEBGL_debug_shaders + // are not enabled by default. + if (!ext.includes('lose_context') && !ext.includes('debug')) { + // Call .getExtension() to enable that extension permanently. + GLctx.getExtension(ext); + } + }); + }, + }; + + var registerWebGlEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + + var webGlEventHandlerFunc = (e = event) => { + if (getWasmTableEntry(callbackfunc)(eventTypeId, 0, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + callbackfunc, + handlerFunc: webGlEventHandlerFunc, + useCapture + }; + JSEvents.registerOrRemoveHandler(eventHandler); + }; + + + function _emscripten_set_webglcontextlost_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + + registerWebGlEventCallback(target, userData, useCapture, callbackfunc, 31, "webglcontextlost", targetThread); + return 0; + ; + } + + + + function _emscripten_set_webglcontextrestored_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + + registerWebGlEventCallback(target, userData, useCapture, callbackfunc, 32, "webglcontextrestored", targetThread); + return 0; + ; + } + + + + + var registerWheelEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + JSEvents.wheelEvent ||= _malloc(96); + + // The DOM Level 3 events spec event 'wheel' + var wheelHandlerFunc = (e = event) => { + var wheelEvent = JSEvents.wheelEvent; + fillMouseEventData(wheelEvent, e, target); + HEAPF64[(((wheelEvent)+(64))/8)] = e["deltaX"]; + HEAPF64[(((wheelEvent)+(72))/8)] = e["deltaY"]; + HEAPF64[(((wheelEvent)+(80))/8)] = e["deltaZ"]; + HEAP32[(((wheelEvent)+(88))/4)] = e["deltaMode"]; + if (((a1, a2, a3) => getWasmTableEntry(callbackfunc).call(null, a1, BigInt(a2), BigInt(a3)))(eventTypeId, wheelEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + allowsDeferredCalls: true, + eventTypeString, + callbackfunc, + handlerFunc: wheelHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + + + function _emscripten_set_wheel_callback_on_thread(target, userData, useCapture, callbackfunc, targetThread) { + target = bigintToI53Checked(target); + userData = bigintToI53Checked(userData); + callbackfunc = bigintToI53Checked(callbackfunc); + targetThread = bigintToI53Checked(targetThread); + + + target = findEventTarget(target); + if (!target) return -4; + if (typeof target.onwheel != 'undefined') { + return registerWheelEventCallback(target, userData, useCapture, callbackfunc, 9, "wheel", targetThread); + } else { + return -1; + } + ; + } + + + var webglPowerPreferences = ["default","low-power","high-performance"]; + + + + /** @suppress {duplicate } */ + var _emscripten_webgl_do_create_context = function(target, attributes) { + target = bigintToI53Checked(target); + attributes = bigintToI53Checked(attributes); + + var ret = (() => { + assert(attributes); + var attr32 = ((attributes)/4); + var powerPreference = HEAP32[attr32 + (8>>2)]; + var contextAttributes = { + 'alpha': !!HEAP8[attributes + 0], + 'depth': !!HEAP8[attributes + 1], + 'stencil': !!HEAP8[attributes + 2], + 'antialias': !!HEAP8[attributes + 3], + 'premultipliedAlpha': !!HEAP8[attributes + 4], + 'preserveDrawingBuffer': !!HEAP8[attributes + 5], + 'powerPreference': webglPowerPreferences[powerPreference], + 'failIfMajorPerformanceCaveat': !!HEAP8[attributes + 12], + // The following are not predefined WebGL context attributes in the WebGL specification, so the property names can be minified by Closure. + majorVersion: HEAP32[attr32 + (16>>2)], + minorVersion: HEAP32[attr32 + (20>>2)], + enableExtensionsByDefault: HEAP8[attributes + 24], + explicitSwapControl: HEAP8[attributes + 25], + proxyContextToMainThread: HEAP32[attr32 + (28>>2)], + renderViaOffscreenBackBuffer: HEAP8[attributes + 32] + }; + + // TODO: Make these into hard errors at some point in the future + if (contextAttributes.majorVersion !== 1 && contextAttributes.majorVersion !== 2) { + err(`Invalid WebGL version requested: ${contextAttributes.majorVersion}`); + } + + var canvas = findCanvasEventTarget(target); + + if (!canvas) { + return 0; + } + + if (contextAttributes.explicitSwapControl) { + return 0; + } + + var contextHandle = GL.createContext(canvas, contextAttributes); + return contextHandle; + })(); + return BigInt(ret); + }; + var _emscripten_webgl_create_context = _emscripten_webgl_do_create_context; + + + function _emscripten_webgl_make_context_current(contextHandle) { + contextHandle = bigintToI53Checked(contextHandle); + + + var success = GL.makeContextCurrent(contextHandle); + return success ? 0 : -5; + ; + } + + 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); + newOffset = bigintToI53Checked(newOffset); + + + return 70; + ; + } + + var printCharBuffers = [null,[],[]]; + + var printChar = (stream, curr) => { + var buffer = printCharBuffers[stream]; + assert(buffer); + if (curr === 0 || 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); + }; + + + + function _fd_write(fd, iov, iovcnt, pnum) { + iov = bigintToI53Checked(iov); + iovcnt = bigintToI53Checked(iovcnt); + pnum = bigintToI53Checked(pnum); + + + // hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0 + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = Number(HEAPU64[((iov)/8)]); + var len = Number(HEAPU64[(((iov)+(8))/8)]); + iov += 16; + for (var j = 0; j < len; j++) { + printChar(fd, HEAPU8[ptr+j]); + } + num += len; + } + HEAPU64[((pnum)/8)] = BigInt(num); + return 0; + ; + } + + var _glActiveTexture = (x0) => GLctx.activeTexture(x0); + + var _glAttachShader = (program, shader) => { + GLctx.attachShader(GL.programs[program], GL.shaders[shader]); + }; + + var _glBindBuffer = (target, buffer) => { + + if (target == 0x88EB /*GL_PIXEL_PACK_BUFFER*/) { + // In WebGL 2 glReadPixels entry point, we need to use a different WebGL 2 + // API function call when a buffer is bound to + // GL_PIXEL_PACK_BUFFER_BINDING point, so must keep track whether that + // binding point is non-null to know what is the proper API function to + // call. + GLctx.currentPixelPackBufferBinding = buffer; + } else if (target == 0x88EC /*GL_PIXEL_UNPACK_BUFFER*/) { + // In WebGL 2 gl(Compressed)Tex(Sub)Image[23]D entry points, we need to + // use a different WebGL 2 API function call when a buffer is bound to + // GL_PIXEL_UNPACK_BUFFER_BINDING point, so must keep track whether that + // binding point is non-null to know what is the proper API function to + // call. + GLctx.currentPixelUnpackBufferBinding = buffer; + } + GLctx.bindBuffer(target, GL.buffers[buffer]); + }; + + var _glBindBufferBase = (target, index, buffer) => { + GLctx.bindBufferBase(target, index, GL.buffers[buffer]); + }; + + var _glBindFramebuffer = (target, framebuffer) => { + + GLctx.bindFramebuffer(target, GL.framebuffers[framebuffer]); + + }; + + var _glBindRenderbuffer = (target, renderbuffer) => { + GLctx.bindRenderbuffer(target, GL.renderbuffers[renderbuffer]); + }; + + var _glBindSampler = (unit, sampler) => { + GLctx.bindSampler(unit, GL.samplers[sampler]); + }; + + var _glBindTexture = (target, texture) => { + GLctx.bindTexture(target, GL.textures[texture]); + }; + + var _glBindVertexArray = (vao) => { + GLctx.bindVertexArray(GL.vaos[vao]); + }; + + var _glBlendColor = (x0, x1, x2, x3) => GLctx.blendColor(x0, x1, x2, x3); + + var _glBlendEquationSeparate = (x0, x1) => GLctx.blendEquationSeparate(x0, x1); + + var _glBlendFuncSeparate = (x0, x1, x2, x3) => GLctx.blendFuncSeparate(x0, x1, x2, x3); + + var _glBlitFramebuffer = (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) => GLctx.blitFramebuffer(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9); + + + function _glBufferData(target, size, data, usage) { + size = bigintToI53Checked(size); + data = bigintToI53Checked(data); + + + + if (GL.currentContext.version >= 2) { + // If size is zero, WebGL would interpret uploading the whole input + // arraybuffer (starting from given offset), which would not make sense in + // WebAssembly, so avoid uploading if size is zero. However we must still + // call bufferData to establish a backing storage of zero bytes. + if (data && size) { + GLctx.bufferData(target, HEAPU8, usage, data, size); + } else { + GLctx.bufferData(target, size, usage); + } + return; + } + // N.b. here first form specifies a heap subarray, second form an integer + // size, so the ?: code here is polymorphic. It is advised to avoid + // randomly mixing both uses in calling code, to avoid any potential JS + // engine JIT issues. + GLctx.bufferData(target, data ? HEAPU8.subarray(data, data+size) : size, usage); + ; + } + + + function _glBufferSubData(target, offset, size, data) { + offset = bigintToI53Checked(offset); + size = bigintToI53Checked(size); + data = bigintToI53Checked(data); + + + if (GL.currentContext.version >= 2) { + size && GLctx.bufferSubData(target, offset, HEAPU8, data, size); + return; + } + GLctx.bufferSubData(target, offset, HEAPU8.subarray(data, data+size)); + ; + } + + var _glClearBufferfi = (x0, x1, x2, x3) => GLctx.clearBufferfi(x0, x1, x2, x3); + + function _glClearBufferfv(buffer, drawbuffer, value) { + value = bigintToI53Checked(value); + + + + GLctx.clearBufferfv(buffer, drawbuffer, HEAPF32, ((value)/4)); + ; + } + + function _glClearBufferiv(buffer, drawbuffer, value) { + value = bigintToI53Checked(value); + + + + GLctx.clearBufferiv(buffer, drawbuffer, HEAP32, ((value)/4)); + ; + } + + var _glColorMask = (red, green, blue, alpha) => { + GLctx.colorMask(!!red, !!green, !!blue, !!alpha); + }; + + var _glCompileShader = (shader) => { + GLctx.compileShader(GL.shaders[shader]); + }; + + + function _glCompressedTexImage2D(target, level, internalFormat, width, height, border, imageSize, data) { + data = bigintToI53Checked(data); + + + // `data` may be null here, which means "allocate uniniitalized space but + // don't upload" in GLES parlance, but `compressedTexImage2D` requires the + // final data parameter, so we simply pass a heap view starting at zero + // effectively uploading whatever happens to be near address zero. See + // https://github.com/emscripten-core/emscripten/issues/19300. + if (GL.currentContext.version >= 2) { + if (GLctx.currentPixelUnpackBufferBinding || !imageSize) { + GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, imageSize, data); + return; + } + GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, HEAPU8, data, imageSize); + return; + } + GLctx.compressedTexImage2D(target, level, internalFormat, width, height, border, HEAPU8.subarray((data), data+imageSize)); + ; + } + + function _glCompressedTexImage3D(target, level, internalFormat, width, height, depth, border, imageSize, data) { + data = bigintToI53Checked(data); + + + if (GLctx.currentPixelUnpackBufferBinding) { + GLctx.compressedTexImage3D(target, level, internalFormat, width, height, depth, border, imageSize, data); + } else { + GLctx.compressedTexImage3D(target, level, internalFormat, width, height, depth, border, HEAPU8, data, imageSize); + } + ; + } + + var _glCreateProgram = () => { + var id = GL.getNewId(GL.programs); + var program = GLctx.createProgram(); + // Store additional information needed for each shader program: + program.name = id; + // Lazy cache results of + // glGetProgramiv(GL_ACTIVE_UNIFORM_MAX_LENGTH/GL_ACTIVE_ATTRIBUTE_MAX_LENGTH/GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH) + program.maxUniformLength = program.maxAttributeLength = program.maxUniformBlockNameLength = 0; + program.uniformIdCounter = 1; + GL.programs[id] = program; + return id; + }; + + var _glCreateShader = (shaderType) => { + var id = GL.getNewId(GL.shaders); + GL.shaders[id] = GLctx.createShader(shaderType); + + return id; + }; + + var _glCullFace = (x0) => GLctx.cullFace(x0); + + + function _glDeleteBuffers(n, buffers) { + buffers = bigintToI53Checked(buffers); + + + for (var i = 0; i < n; i++) { + var id = HEAP32[(((buffers)+(i*4))/4)]; + var buffer = GL.buffers[id]; + + // From spec: "glDeleteBuffers silently ignores 0's and names that do not + // correspond to existing buffer objects." + if (!buffer) continue; + + GLctx.deleteBuffer(buffer); + buffer.name = 0; + GL.buffers[id] = null; + + if (id == GLctx.currentPixelPackBufferBinding) GLctx.currentPixelPackBufferBinding = 0; + if (id == GLctx.currentPixelUnpackBufferBinding) GLctx.currentPixelUnpackBufferBinding = 0; + } + ; + } + + + function _glDeleteFramebuffers(n, framebuffers) { + framebuffers = bigintToI53Checked(framebuffers); + + + for (var i = 0; i < n; ++i) { + var id = HEAP32[(((framebuffers)+(i*4))/4)]; + var framebuffer = GL.framebuffers[id]; + if (!framebuffer) continue; // GL spec: "glDeleteFramebuffers silently ignores 0s and names that do not correspond to existing framebuffer objects". + GLctx.deleteFramebuffer(framebuffer); + framebuffer.name = 0; + GL.framebuffers[id] = null; + } + ; + } + + var _glDeleteProgram = (id) => { + if (!id) return; + var program = GL.programs[id]; + if (!program) { + // glDeleteProgram actually signals an error when deleting a nonexisting + // object, unlike some other GL delete functions. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + GLctx.deleteProgram(program); + program.name = 0; + GL.programs[id] = null; + }; + + + function _glDeleteRenderbuffers(n, renderbuffers) { + renderbuffers = bigintToI53Checked(renderbuffers); + + + for (var i = 0; i < n; i++) { + var id = HEAP32[(((renderbuffers)+(i*4))/4)]; + var renderbuffer = GL.renderbuffers[id]; + if (!renderbuffer) continue; // GL spec: "glDeleteRenderbuffers silently ignores 0s and names that do not correspond to existing renderbuffer objects". + GLctx.deleteRenderbuffer(renderbuffer); + renderbuffer.name = 0; + GL.renderbuffers[id] = null; + } + ; + } + + function _glDeleteSamplers(n, samplers) { + samplers = bigintToI53Checked(samplers); + + + for (var i = 0; i < n; i++) { + var id = HEAP32[(((samplers)+(i*4))/4)]; + var sampler = GL.samplers[id]; + if (!sampler) continue; + GLctx.deleteSampler(sampler); + sampler.name = 0; + GL.samplers[id] = null; + } + ; + } + + var _glDeleteShader = (id) => { + if (!id) return; + var shader = GL.shaders[id]; + if (!shader) { + // glDeleteShader actually signals an error when deleting a nonexisting + // object, unlike some other GL delete functions. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + GLctx.deleteShader(shader); + GL.shaders[id] = null; + }; + + + function _glDeleteTextures(n, textures) { + textures = bigintToI53Checked(textures); + + + for (var i = 0; i < n; i++) { + var id = HEAP32[(((textures)+(i*4))/4)]; + var texture = GL.textures[id]; + // GL spec: "glDeleteTextures silently ignores 0s and names that do not + // correspond to existing textures". + if (!texture) continue; + GLctx.deleteTexture(texture); + texture.name = 0; + GL.textures[id] = null; + } + ; + } + + + function _glDeleteVertexArrays(n, vaos) { + vaos = bigintToI53Checked(vaos); + + + for (var i = 0; i < n; i++) { + var id = HEAP32[(((vaos)+(i*4))/4)]; + GLctx.deleteVertexArray(GL.vaos[id]); + GL.vaos[id] = null; + } + ; + } + + var _glDepthFunc = (x0) => GLctx.depthFunc(x0); + + var _glDepthMask = (flag) => { + GLctx.depthMask(!!flag); + }; + + var _glDisable = (x0) => GLctx.disable(x0); + + var _glDisableVertexAttribArray = (index) => { + GLctx.disableVertexAttribArray(index); + }; + + var _glDrawArrays = (mode, first, count) => { + + GLctx.drawArrays(mode, first, count); + + }; + + var _glDrawArraysInstanced = (mode, first, count, primcount) => { + GLctx.drawArraysInstanced(mode, first, count, primcount); + }; + + + function _glDrawElements(mode, count, type, indices) { + indices = bigintToI53Checked(indices); + + + + GLctx.drawElements(mode, count, type, indices); + + ; + } + + + function _glDrawElementsInstanced(mode, count, type, indices, primcount) { + indices = bigintToI53Checked(indices); + + + GLctx.drawElementsInstanced(mode, count, type, indices, primcount); + ; + } + + var _glEnable = (x0) => GLctx.enable(x0); + + var _glEnableVertexAttribArray = (index) => { + GLctx.enableVertexAttribArray(index); + }; + + var _glFrontFace = (x0) => GLctx.frontFace(x0); + + + function _glGenBuffers(n, buffers) { + buffers = bigintToI53Checked(buffers); + + + GL.genObject(n, buffers, 'createBuffer', GL.buffers + ); + ; + } + + + function _glGenRenderbuffers(n, renderbuffers) { + renderbuffers = bigintToI53Checked(renderbuffers); + + + GL.genObject(n, renderbuffers, 'createRenderbuffer', GL.renderbuffers + ); + ; + } + + function _glGenSamplers(n, samplers) { + samplers = bigintToI53Checked(samplers); + + + GL.genObject(n, samplers, 'createSampler', GL.samplers + ); + ; + } + + + function _glGenTextures(n, textures) { + textures = bigintToI53Checked(textures); + + + GL.genObject(n, textures, 'createTexture', GL.textures + ); + ; + } + + + function _glGenVertexArrays(n, arrays) { + arrays = bigintToI53Checked(arrays); + + + GL.genObject(n, arrays, 'createVertexArray', GL.vaos + ); + ; + } + + + + function _glGetAttribLocation(program, name) { + name = bigintToI53Checked(name); + + return GLctx.getAttribLocation(GL.programs[program], UTF8ToString(name)); + } + + var _glGetError = () => { + var error = GLctx.getError() || GL.lastError; + GL.lastError = 0/*GL_NO_ERROR*/; + return error; + }; + + var readI53FromI64 = (ptr) => { + return HEAPU32[((ptr)/4)] + HEAP32[(((ptr)+(4))/4)] * 4294967296; + }; + + var readI53FromU64 = (ptr) => { + return HEAPU32[((ptr)/4)] + HEAPU32[(((ptr)+(4))/4)] * 4294967296; + }; + var writeI53ToI64 = (ptr, num) => { + HEAPU32[((ptr)/4)] = num; + var lower = HEAPU32[((ptr)/4)]; + HEAPU32[(((ptr)+(4))/4)] = (num - lower)/4294967296; + var deserialized = (num >= 0) ? readI53FromU64(ptr) : readI53FromI64(ptr); + var offset = ((ptr)/4); + if (deserialized != num) warnOnce(`writeI53ToI64() out of range: serialized JS Number ${num} to Wasm heap as bytes lo=${ptrToString(HEAPU32[offset])}, hi=${ptrToString(HEAPU32[offset+1])}, which deserializes back to ${deserialized} instead!`); + }; + + + var webglGetExtensions = () => { + var exts = getEmscriptenSupportedExtensions(GLctx); + exts = exts.concat(exts.map((e) => "GL_" + e)); + return exts; + }; + + var emscriptenWebGLGet = (name_, p, type) => { + // Guard against user passing a null pointer. + // Note that GLES2 spec does not say anything about how passing a null + // pointer should be treated. Testing on desktop core GL 3, the application + // crashes on glGetIntegerv to a null pointer, but better to report an error + // instead of doing anything random. + if (!p) { + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + var ret = undefined; + switch (name_) { // Handle a few trivial GLES values + case 0x8DFA: // GL_SHADER_COMPILER + ret = 1; + break; + case 0x8DF8: // GL_SHADER_BINARY_FORMATS + if (type != 0 && type != 1) { + GL.recordError(0x500); // GL_INVALID_ENUM + } + // Do not write anything to the out pointer, since no binary formats are + // supported. + return; + case 0x87FE: // GL_NUM_PROGRAM_BINARY_FORMATS + case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS + ret = 0; + break; + case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS + // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete + // since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be + // queried for length), so implement it ourselves to allow C++ GLES2 + // code get the length. + var formats = GLctx.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); + ret = formats ? formats.length : 0; + break; + + case 0x821D: // GL_NUM_EXTENSIONS + if (GL.currentContext.version < 2) { + // Calling GLES3/WebGL2 function with a GLES2/WebGL1 context + GL.recordError(0x502 /* GL_INVALID_OPERATION */); + return; + } + ret = webglGetExtensions().length; + break; + case 0x821B: // GL_MAJOR_VERSION + case 0x821C: // GL_MINOR_VERSION + if (GL.currentContext.version < 2) { + GL.recordError(0x500); // GL_INVALID_ENUM + return; + } + ret = name_ == 0x821B ? 3 : 0; // return version 3.0 + break; + } + + if (ret === undefined) { + var result = GLctx.getParameter(name_); + switch (typeof result) { + case "number": + ret = result; + break; + case "boolean": + ret = result ? 1 : 0; + break; + case "string": + GL.recordError(0x500); // GL_INVALID_ENUM + return; + case "object": + if (result === null) { + // null is a valid result for some (e.g., which buffer is bound - + // perhaps nothing is bound), but otherwise can mean an invalid + // name_, which we need to report as an error + switch (name_) { + case 0x8894: // ARRAY_BUFFER_BINDING + case 0x8B8D: // CURRENT_PROGRAM + case 0x8895: // ELEMENT_ARRAY_BUFFER_BINDING + case 0x8CA6: // FRAMEBUFFER_BINDING or DRAW_FRAMEBUFFER_BINDING + case 0x8CA7: // RENDERBUFFER_BINDING + case 0x8069: // TEXTURE_BINDING_2D + case 0x85B5: // WebGL 2 GL_VERTEX_ARRAY_BINDING, or WebGL 1 extension OES_vertex_array_object GL_VERTEX_ARRAY_BINDING_OES + case 0x8F36: // COPY_READ_BUFFER_BINDING or COPY_READ_BUFFER + case 0x8F37: // COPY_WRITE_BUFFER_BINDING or COPY_WRITE_BUFFER + case 0x88ED: // PIXEL_PACK_BUFFER_BINDING + case 0x88EF: // PIXEL_UNPACK_BUFFER_BINDING + case 0x8CAA: // READ_FRAMEBUFFER_BINDING + case 0x8919: // SAMPLER_BINDING + case 0x8C1D: // TEXTURE_BINDING_2D_ARRAY + case 0x806A: // TEXTURE_BINDING_3D + case 0x8E25: // TRANSFORM_FEEDBACK_BINDING + case 0x8C8F: // TRANSFORM_FEEDBACK_BUFFER_BINDING + case 0x8A28: // UNIFORM_BUFFER_BINDING + case 0x8514: { // TEXTURE_BINDING_CUBE_MAP + ret = 0; + break; + } + default: { + GL.recordError(0x500); // GL_INVALID_ENUM + return; + } + } + } else if (result instanceof Float32Array || + result instanceof Uint32Array || + result instanceof Int32Array || + result instanceof Array) { + for (var i = 0; i < result.length; ++i) { + switch (type) { + case 0: HEAP32[(((p)+(i*4))/4)] = result[i]; break; + case 2: HEAPF32[(((p)+(i*4))/4)] = result[i]; break; + case 4: HEAP8[(p)+(i)] = result[i] ? 1 : 0; break; + } + } + return; + } else { + try { + ret = result.name | 0; + } catch(e) { + GL.recordError(0x500); // GL_INVALID_ENUM + err(`GL_INVALID_ENUM in glGet${type}v: Unknown object returned from WebGL getParameter(${name_})! (error: ${e})`); + return; + } + } + break; + default: + GL.recordError(0x500); // GL_INVALID_ENUM + err(`GL_INVALID_ENUM in glGet${type}v: Native code calling glGet${type}v(${name_}) and it returns ${result} of type ${typeof(result)}!`); + return; + } + } + + switch (type) { + case 1: writeI53ToI64(p, ret); break; + case 0: HEAP32[((p)/4)] = ret; break; + case 2: HEAPF32[((p)/4)] = ret; break; + case 4: HEAP8[p] = ret ? 1 : 0; break; + } + }; + + + function _glGetIntegerv(name_, p) { + p = bigintToI53Checked(p); + + return emscriptenWebGLGet(name_, p, 0); + } + + + function _glGetProgramInfoLog(program, maxLength, length, infoLog) { + length = bigintToI53Checked(length); + infoLog = bigintToI53Checked(infoLog); + + + var log = GLctx.getProgramInfoLog(GL.programs[program]); + if (log === null) log = '(unknown error)'; + var numBytesWrittenExclNull = (maxLength > 0 && infoLog) ? stringToUTF8(log, infoLog, maxLength) : 0; + if (length) HEAP32[((length)/4)] = numBytesWrittenExclNull; + ; + } + + + function _glGetProgramiv(program, pname, p) { + p = bigintToI53Checked(p); + + + if (!p) { + // GLES2 specification does not specify how to behave if p is a null + // pointer. Since calling this function does not make sense if p == null, + // issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + + if (program >= GL.counter) { + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + + program = GL.programs[program]; + + if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH + var log = GLctx.getProgramInfoLog(program); + if (log === null) log = '(unknown error)'; + HEAP32[((p)/4)] = log.length + 1; + } else if (pname == 0x8B87 /* GL_ACTIVE_UNIFORM_MAX_LENGTH */) { + if (!program.maxUniformLength) { + var numActiveUniforms = GLctx.getProgramParameter(program, 0x8B86/*GL_ACTIVE_UNIFORMS*/); + for (var i = 0; i < numActiveUniforms; ++i) { + program.maxUniformLength = Math.max(program.maxUniformLength, GLctx.getActiveUniform(program, i).name.length+1); + } + } + HEAP32[((p)/4)] = program.maxUniformLength; + } else if (pname == 0x8B8A /* GL_ACTIVE_ATTRIBUTE_MAX_LENGTH */) { + if (!program.maxAttributeLength) { + var numActiveAttributes = GLctx.getProgramParameter(program, 0x8B89/*GL_ACTIVE_ATTRIBUTES*/); + for (var i = 0; i < numActiveAttributes; ++i) { + program.maxAttributeLength = Math.max(program.maxAttributeLength, GLctx.getActiveAttrib(program, i).name.length+1); + } + } + HEAP32[((p)/4)] = program.maxAttributeLength; + } else if (pname == 0x8A35 /* GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH */) { + if (!program.maxUniformBlockNameLength) { + var numActiveUniformBlocks = GLctx.getProgramParameter(program, 0x8A36/*GL_ACTIVE_UNIFORM_BLOCKS*/); + for (var i = 0; i < numActiveUniformBlocks; ++i) { + program.maxUniformBlockNameLength = Math.max(program.maxUniformBlockNameLength, GLctx.getActiveUniformBlockName(program, i).length+1); + } + } + HEAP32[((p)/4)] = program.maxUniformBlockNameLength; + } else { + HEAP32[((p)/4)] = GLctx.getProgramParameter(program, pname); + } + ; + } + + + + function _glGetShaderInfoLog(shader, maxLength, length, infoLog) { + length = bigintToI53Checked(length); + infoLog = bigintToI53Checked(infoLog); + + + var log = GLctx.getShaderInfoLog(GL.shaders[shader]); + if (log === null) log = '(unknown error)'; + var numBytesWrittenExclNull = (maxLength > 0 && infoLog) ? stringToUTF8(log, infoLog, maxLength) : 0; + if (length) HEAP32[((length)/4)] = numBytesWrittenExclNull; + ; + } + + + function _glGetShaderiv(shader, pname, p) { + p = bigintToI53Checked(p); + + + if (!p) { + // GLES2 specification does not specify how to behave if p is a null + // pointer. Since calling this function does not make sense if p == null, + // issue a GL error to notify user about it. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + return; + } + if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH + var log = GLctx.getShaderInfoLog(GL.shaders[shader]); + if (log === null) log = '(unknown error)'; + // The GLES2 specification says that if the shader has an empty info log, + // a value of 0 is returned. Otherwise the log has a null char appended. + // (An empty string is falsey, so we can just check that instead of + // looking at log.length.) + var logLength = log ? log.length + 1 : 0; + HEAP32[((p)/4)] = logLength; + } else if (pname == 0x8B88) { // GL_SHADER_SOURCE_LENGTH + var source = GLctx.getShaderSource(GL.shaders[shader]); + // source may be a null, or the empty string, both of which are falsey + // values that we report a 0 length for. + var sourceLength = source ? source.length + 1 : 0; + HEAP32[((p)/4)] = sourceLength; + } else { + HEAP32[((p)/4)] = GLctx.getShaderParameter(GL.shaders[shader], pname); + } + ; + } + + + 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 stringToNewUTF8 = (str) => { + var size = lengthBytesUTF8(str) + 1; + var ret = _malloc(size); + if (ret) stringToUTF8(str, ret, size); + return ret; + }; + + var _glGetStringi = function(name, index) { + + var ret = (() => { + if (GL.currentContext.version < 2) { + GL.recordError(0x502 /* GL_INVALID_OPERATION */); // Calling GLES3/WebGL2 function with a GLES2/WebGL1 context + return 0; + } + var stringiCache = GL.stringiCache[name]; + if (stringiCache) { + if (index < 0 || index >= stringiCache.length) { + GL.recordError(0x501/*GL_INVALID_VALUE*/); + return 0; + } + return stringiCache[index]; + } + switch (name) { + case 0x1F03 /* GL_EXTENSIONS */: + var exts = webglGetExtensions().map(stringToNewUTF8); + stringiCache = GL.stringiCache[name] = exts; + if (index < 0 || index >= stringiCache.length) { + GL.recordError(0x501/*GL_INVALID_VALUE*/); + return 0; + } + return stringiCache[index]; + default: + GL.recordError(0x500/*GL_INVALID_ENUM*/); + return 0; + } + })(); + return BigInt(ret); + }; + + /** @suppress {checkTypes} */ + var jstoi_q = (str) => parseInt(str); + + /** @noinline */ + var webglGetLeftBracePos = (name) => name.slice(-1) == ']' && name.lastIndexOf('['); + + var webglPrepareUniformLocationsBeforeFirstUse = (program) => { + var uniformLocsById = program.uniformLocsById, // Maps GLuint -> WebGLUniformLocation + uniformSizeAndIdsByName = program.uniformSizeAndIdsByName, // Maps name -> [uniform array length, GLuint] + i, j; + + // On the first time invocation of glGetUniformLocation on this shader program: + // initialize cache data structures and discover which uniforms are arrays. + if (!uniformLocsById) { + // maps GLint integer locations to WebGLUniformLocations + program.uniformLocsById = uniformLocsById = {}; + // maps integer locations back to uniform name strings, so that we can lazily fetch uniform array locations + program.uniformArrayNamesById = {}; + + var numActiveUniforms = GLctx.getProgramParameter(program, 0x8B86/*GL_ACTIVE_UNIFORMS*/); + for (i = 0; i < numActiveUniforms; ++i) { + var u = GLctx.getActiveUniform(program, i); + var nm = u.name; + var sz = u.size; + var lb = webglGetLeftBracePos(nm); + var arrayName = lb > 0 ? nm.slice(0, lb) : nm; + + // Assign a new location. + var id = program.uniformIdCounter; + program.uniformIdCounter += sz; + // Eagerly get the location of the uniformArray[0] base element. + // The remaining indices >0 will be left for lazy evaluation to + // improve performance. Those may never be needed to fetch, if the + // application fills arrays always in full starting from the first + // element of the array. + uniformSizeAndIdsByName[arrayName] = [sz, id]; + + // Store placeholder integers in place that highlight that these + // >0 index locations are array indices pending population. + for (j = 0; j < sz; ++j) { + uniformLocsById[id] = j; + program.uniformArrayNamesById[id++] = arrayName; + } + } + } + }; + + + + + function _glGetUniformLocation(program, name) { + name = bigintToI53Checked(name); + + + + name = UTF8ToString(name); + + if (program = GL.programs[program]) { + webglPrepareUniformLocationsBeforeFirstUse(program); + var uniformLocsById = program.uniformLocsById; // Maps GLuint -> WebGLUniformLocation + var arrayIndex = 0; + var uniformBaseName = name; + + // Invariant: when populating integer IDs for uniform locations, we must + // maintain the precondition that arrays reside in contiguous addresses, + // i.e. for a 'vec4 colors[10];', colors[4] must be at location + // colors[0]+4. However, user might call glGetUniformLocation(program, + // "colors") for an array, so we cannot discover based on the user input + // arguments whether the uniform we are dealing with is an array. The only + // way to discover which uniforms are arrays is to enumerate over all the + // active uniforms in the program. + var leftBrace = webglGetLeftBracePos(name); + + // If user passed an array accessor "[index]", parse the array index off the accessor. + if (leftBrace > 0) { + arrayIndex = jstoi_q(name.slice(leftBrace + 1)) >>> 0; // "index]", coerce parseInt(']') with >>>0 to treat "foo[]" as "foo[0]" and foo[-1] as unsigned out-of-bounds. + uniformBaseName = name.slice(0, leftBrace); + } + + // Have we cached the location of this uniform before? + // A pair [array length, GLint of the uniform location] + var sizeAndId = program.uniformSizeAndIdsByName[uniformBaseName]; + + // If an uniform with this name exists, and if its index is within the + // array limits (if it's even an array), query the WebGLlocation, or + // return an existing cached location. + if (sizeAndId && arrayIndex < sizeAndId[0]) { + arrayIndex += sizeAndId[1]; // Add the base location of the uniform to the array index offset. + if ((uniformLocsById[arrayIndex] = uniformLocsById[arrayIndex] || GLctx.getUniformLocation(program, name))) { + return arrayIndex; + } + } + } + else { + // N.b. we are currently unable to distinguish between GL program IDs that + // never existed vs GL program IDs that have been deleted, so report + // GL_INVALID_VALUE in both cases. + GL.recordError(0x501 /* GL_INVALID_VALUE */); + } + return -1; + ; + } + + var tempFixedLengthArray = []; + + function _glInvalidateFramebuffer(target, numAttachments, attachments) { + attachments = bigintToI53Checked(attachments); + + + var list = tempFixedLengthArray[numAttachments]; + for (var i = 0; i < numAttachments; i++) { + list[i] = HEAP32[(((attachments)+(i*4))/4)]; + } + + GLctx.invalidateFramebuffer(target, list); + ; + } + + var _glLinkProgram = (program) => { + program = GL.programs[program]; + GLctx.linkProgram(program); + // Invalidate earlier computed uniform->ID mappings, those have now become stale + program.uniformLocsById = 0; // Mark as null-like so that glGetUniformLocation() knows to populate this again. + program.uniformSizeAndIdsByName = {}; + + }; + + var _glPixelStorei = (pname, param) => { + if (pname == 3317) { + GL.unpackAlignment = param; + } else if (pname == 3314) { + GL.unpackRowLength = param; + } + GLctx.pixelStorei(pname, param); + }; + + var _glPolygonOffset = (x0, x1) => GLctx.polygonOffset(x0, x1); + + var _glReadBuffer = (x0) => GLctx.readBuffer(x0); + + var _glRenderbufferStorageMultisample = (x0, x1, x2, x3, x4) => GLctx.renderbufferStorageMultisample(x0, x1, x2, x3, x4); + + var _glSamplerParameterf = (sampler, pname, param) => { + GLctx.samplerParameterf(GL.samplers[sampler], pname, param); + }; + + var _glSamplerParameteri = (sampler, pname, param) => { + GLctx.samplerParameteri(GL.samplers[sampler], pname, param); + }; + + var _glScissor = (x0, x1, x2, x3) => GLctx.scissor(x0, x1, x2, x3); + + + function _glShaderSource(shader, count, string, length) { + string = bigintToI53Checked(string); + length = bigintToI53Checked(length); + + + var source = GL.getSource(shader, count, string, length); + + GLctx.shaderSource(GL.shaders[shader], source); + ; + } + + var _glStencilFunc = (x0, x1, x2) => GLctx.stencilFunc(x0, x1, x2); + + var _glStencilFuncSeparate = (x0, x1, x2, x3) => GLctx.stencilFuncSeparate(x0, x1, x2, x3); + + var _glStencilMask = (x0) => GLctx.stencilMask(x0); + + var _glStencilOp = (x0, x1, x2) => GLctx.stencilOp(x0, x1, x2); + + var _glStencilOpSeparate = (x0, x1, x2, x3) => GLctx.stencilOpSeparate(x0, x1, x2, x3); + + var computeUnpackAlignedImageSize = (width, height, sizePerPixel) => { + function roundedToNextMultipleOf(x, y) { + return (x + y - 1) & -y; + } + var plainRowSize = (GL.unpackRowLength || width) * sizePerPixel; + var alignedRowSize = roundedToNextMultipleOf(plainRowSize, GL.unpackAlignment); + return height * alignedRowSize; + }; + + var colorChannelsInGlTextureFormat = (format) => { + // Micro-optimizations for size: map format to size by subtracting smallest + // enum value (0x1902) from all values first. Also omit the most common + // size value (1) from the list, which is assumed by formats not on the + // list. + var colorChannels = { + // 0x1902 /* GL_DEPTH_COMPONENT */ - 0x1902: 1, + // 0x1906 /* GL_ALPHA */ - 0x1902: 1, + 5: 3, + 6: 4, + // 0x1909 /* GL_LUMINANCE */ - 0x1902: 1, + 8: 2, + 29502: 3, + 29504: 4, + // 0x1903 /* GL_RED */ - 0x1902: 1, + 26917: 2, + 26918: 2, + // 0x8D94 /* GL_RED_INTEGER */ - 0x1902: 1, + 29846: 3, + 29847: 4 + }; + return colorChannels[format - 0x1902]||1; + }; + + var heapObjectForWebGLType = (type) => { + // Micro-optimization for size: Subtract lowest GL enum number (0x1400/* GL_BYTE */) from type to compare + // smaller values for the heap, for shorter generated code size. + // Also the type HEAPU16 is not tested for explicitly, but any unrecognized type will return out HEAPU16. + // (since most types are HEAPU16) + type -= 0x1400; + if (type == 0) return HEAP8; + + if (type == 1) return HEAPU8; + + if (type == 2) return HEAP16; + + if (type == 4) return HEAP32; + + if (type == 6) return HEAPF32; + + if (type == 5 + || type == 28922 + || type == 28520 + || type == 30779 + || type == 30782 + ) + return HEAPU32; + + return HEAPU16; + }; + + var toTypedArrayIndex = (pointer, heap) => + pointer / heap.BYTES_PER_ELEMENT; + + var emscriptenWebGLGetTexPixelData = (type, format, width, height, pixels, internalFormat) => { + var heap = heapObjectForWebGLType(type); + var sizePerPixel = colorChannelsInGlTextureFormat(format) * heap.BYTES_PER_ELEMENT; + var bytes = computeUnpackAlignedImageSize(width, height, sizePerPixel); + return heap.subarray(toTypedArrayIndex(pixels, heap), toTypedArrayIndex(pixels + bytes, heap)); + }; + + + + + function _glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels) { + pixels = bigintToI53Checked(pixels); + + + if (GL.currentContext.version >= 2) { + if (GLctx.currentPixelUnpackBufferBinding) { + GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixels); + return; + } + if (pixels) { + var heap = heapObjectForWebGLType(type); + var index = toTypedArrayIndex(pixels, heap); + GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, heap, index); + return; + } + } + var pixelData = pixels ? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, internalFormat) : null; + GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixelData); + ; + } + + + + function _glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels) { + pixels = bigintToI53Checked(pixels); + + + if (GLctx.currentPixelUnpackBufferBinding) { + GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels); + } else if (pixels) { + var heap = heapObjectForWebGLType(type); + GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, heap, toTypedArrayIndex(pixels, heap)); + } else { + GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, null); + } + ; + } + + var _glTexParameteri = (x0, x1, x2) => GLctx.texParameteri(x0, x1, x2); + + var _glTexStorage2D = (x0, x1, x2, x3, x4) => GLctx.texStorage2D(x0, x1, x2, x3, x4); + + var _glTexStorage3D = (x0, x1, x2, x3, x4, x5) => GLctx.texStorage3D(x0, x1, x2, x3, x4, x5); + + + + + + function _glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels) { + pixels = bigintToI53Checked(pixels); + + + if (GL.currentContext.version >= 2) { + if (GLctx.currentPixelUnpackBufferBinding) { + GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); + return; + } + if (pixels) { + var heap = heapObjectForWebGLType(type); + GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, heap, toTypedArrayIndex(pixels, heap)); + return; + } + } + var pixelData = pixels ? emscriptenWebGLGetTexPixelData(type, format, width, height, pixels, 0) : null; + GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixelData); + ; + } + + + + function _glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels) { + pixels = bigintToI53Checked(pixels); + + + if (GLctx.currentPixelUnpackBufferBinding) { + GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); + } else if (pixels) { + var heap = heapObjectForWebGLType(type); + GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, heap, toTypedArrayIndex(pixels, heap)); + } else { + GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, null); + } + ; + } + + var webglGetUniformLocation = (location) => { + var p = GLctx.currentProgram; + + if (p) { + var webglLoc = p.uniformLocsById[location]; + // p.uniformLocsById[location] stores either an integer, or a + // WebGLUniformLocation. + // If an integer, we have not yet bound the location, so do it now. The + // integer value specifies the array index we should bind to. + if (typeof webglLoc == 'number') { + p.uniformLocsById[location] = webglLoc = GLctx.getUniformLocation(p, p.uniformArrayNamesById[location] + (webglLoc > 0 ? `[${webglLoc}]` : '')); + } + // Else an already cached WebGLUniformLocation, return it. + return webglLoc; + } else { + GL.recordError(0x502/*GL_INVALID_OPERATION*/); + } + }; + + var miniTempWebGLFloatBuffers = []; + + + function _glUniform1fv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform1fv(webglGetUniformLocation(location), HEAPF32, ((value)/4), count); + return; + } + + if (count <= 288) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; ++i) { + view[i] = HEAPF32[(((value)+(4*i))/4)]; + } + } else + { + var view = HEAPF32.subarray((((value)/4)), ((value+count*4)/4)); + } + GLctx.uniform1fv(webglGetUniformLocation(location), view); + ; + } + + + var _glUniform1i = (location, v0) => { + GLctx.uniform1i(webglGetUniformLocation(location), v0); + }; + + + var miniTempWebGLIntBuffers = []; + + + function _glUniform1iv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform1iv(webglGetUniformLocation(location), HEAP32, ((value)/4), count); + return; + } + + if (count <= 288) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; ++i) { + view[i] = HEAP32[(((value)+(4*i))/4)]; + } + } else + { + var view = HEAP32.subarray((((value)/4)), ((value+count*4)/4)); + } + GLctx.uniform1iv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniform2fv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform2fv(webglGetUniformLocation(location), HEAPF32, ((value)/4), count*2); + return; + } + + if (count <= 144) { + // avoid allocation when uploading few enough uniforms + count *= 2; + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; i += 2) { + view[i] = HEAPF32[(((value)+(4*i))/4)]; + view[i+1] = HEAPF32[(((value)+(4*i+4))/4)]; + } + } else + { + var view = HEAPF32.subarray((((value)/4)), ((value+count*8)/4)); + } + GLctx.uniform2fv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniform2iv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform2iv(webglGetUniformLocation(location), HEAP32, ((value)/4), count*2); + return; + } + + if (count <= 144) { + // avoid allocation when uploading few enough uniforms + count *= 2; + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; i += 2) { + view[i] = HEAP32[(((value)+(4*i))/4)]; + view[i+1] = HEAP32[(((value)+(4*i+4))/4)]; + } + } else + { + var view = HEAP32.subarray((((value)/4)), ((value+count*8)/4)); + } + GLctx.uniform2iv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniform3fv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform3fv(webglGetUniformLocation(location), HEAPF32, ((value)/4), count*3); + return; + } + + if (count <= 96) { + // avoid allocation when uploading few enough uniforms + count *= 3; + var view = miniTempWebGLFloatBuffers[count]; + for (var i = 0; i < count; i += 3) { + view[i] = HEAPF32[(((value)+(4*i))/4)]; + view[i+1] = HEAPF32[(((value)+(4*i+4))/4)]; + view[i+2] = HEAPF32[(((value)+(4*i+8))/4)]; + } + } else + { + var view = HEAPF32.subarray((((value)/4)), ((value+count*12)/4)); + } + GLctx.uniform3fv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniform3iv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform3iv(webglGetUniformLocation(location), HEAP32, ((value)/4), count*3); + return; + } + + if (count <= 96) { + // avoid allocation when uploading few enough uniforms + count *= 3; + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; i += 3) { + view[i] = HEAP32[(((value)+(4*i))/4)]; + view[i+1] = HEAP32[(((value)+(4*i+4))/4)]; + view[i+2] = HEAP32[(((value)+(4*i+8))/4)]; + } + } else + { + var view = HEAP32.subarray((((value)/4)), ((value+count*12)/4)); + } + GLctx.uniform3iv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniform4fv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform4fv(webglGetUniformLocation(location), HEAPF32, ((value)/4), count*4); + return; + } + + if (count <= 72) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLFloatBuffers[4*count]; + // hoist the heap out of the loop for size and for pthreads+growth. + var heap = HEAPF32; + value = ((value)/4); + count *= 4; + for (var i = 0; i < count; i += 4) { + var dst = value + i; + view[i] = heap[dst]; + view[i + 1] = heap[dst + 1]; + view[i + 2] = heap[dst + 2]; + view[i + 3] = heap[dst + 3]; + } + } else + { + var view = HEAPF32.subarray((((value)/4)), ((value+count*16)/4)); + } + GLctx.uniform4fv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniform4iv(location, count, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniform4iv(webglGetUniformLocation(location), HEAP32, ((value)/4), count*4); + return; + } + + if (count <= 72) { + // avoid allocation when uploading few enough uniforms + count *= 4; + var view = miniTempWebGLIntBuffers[count]; + for (var i = 0; i < count; i += 4) { + view[i] = HEAP32[(((value)+(4*i))/4)]; + view[i+1] = HEAP32[(((value)+(4*i+4))/4)]; + view[i+2] = HEAP32[(((value)+(4*i+8))/4)]; + view[i+3] = HEAP32[(((value)+(4*i+12))/4)]; + } + } else + { + var view = HEAP32.subarray((((value)/4)), ((value+count*16)/4)); + } + GLctx.uniform4iv(webglGetUniformLocation(location), view); + ; + } + + + + + function _glUniformMatrix4fv(location, count, transpose, value) { + value = bigintToI53Checked(value); + + + + if (GL.currentContext.version >= 2) { + count && GLctx.uniformMatrix4fv(webglGetUniformLocation(location), !!transpose, HEAPF32, ((value)/4), count*16); + return; + } + + if (count <= 18) { + // avoid allocation when uploading few enough uniforms + var view = miniTempWebGLFloatBuffers[16*count]; + // hoist the heap out of the loop for size and for pthreads+growth. + var heap = HEAPF32; + value = ((value)/4); + count *= 16; + for (var i = 0; i < count; i += 16) { + var dst = value + i; + view[i] = heap[dst]; + view[i + 1] = heap[dst + 1]; + view[i + 2] = heap[dst + 2]; + view[i + 3] = heap[dst + 3]; + view[i + 4] = heap[dst + 4]; + view[i + 5] = heap[dst + 5]; + view[i + 6] = heap[dst + 6]; + view[i + 7] = heap[dst + 7]; + view[i + 8] = heap[dst + 8]; + view[i + 9] = heap[dst + 9]; + view[i + 10] = heap[dst + 10]; + view[i + 11] = heap[dst + 11]; + view[i + 12] = heap[dst + 12]; + view[i + 13] = heap[dst + 13]; + view[i + 14] = heap[dst + 14]; + view[i + 15] = heap[dst + 15]; + } + } else + { + var view = HEAPF32.subarray((((value)/4)), ((value+count*64)/4)); + } + GLctx.uniformMatrix4fv(webglGetUniformLocation(location), !!transpose, view); + ; + } + + var _glUseProgram = (program) => { + program = GL.programs[program]; + GLctx.useProgram(program); + // Record the currently active program so that we can access the uniform + // mapping table of that program. + GLctx.currentProgram = program; + }; + + var _glVertexAttribDivisor = (index, divisor) => { + GLctx.vertexAttribDivisor(index, divisor); + }; + + function _glVertexAttribIPointer(index, size, type, stride, ptr) { + ptr = bigintToI53Checked(ptr); + + + GLctx.vertexAttribIPointer(index, size, type, stride, ptr); + ; + } + + + function _glVertexAttribPointer(index, size, type, normalized, stride, ptr) { + ptr = bigintToI53Checked(ptr); + + + GLctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); + ; + } + + var _glViewport = (x0, x1, x2, x3) => GLctx.viewport(x0, x1, x2, x3); + + var _wasm_debug_break = () => { + debugger; + }; + + var _wasm_write_string = (s_count, s_data, log_flag) => { + function js_string_from_jai_string(pointer, length) { + const text_decoder = new TextDecoder(); + const u8 = new Uint8Array(wasmMemory.buffer) + const bytes = u8.subarray(Number(pointer), Number(pointer) + Number(length)); + return text_decoder.decode(bytes); + } + + const string = js_string_from_jai_string(s_data, s_count); + + switch (log_flag) { + case /* ERROR */ 0x1: { console.error(string); } break; + case /* WARNING */ 0x2: { console.warn(string); } break; + case /* CONTENT */ 0x4: { console.info(`%c${string}`, "color: #3949ab;"); } break; + default: { console.log(string); } + } + // Module.print(string); + }; + + + + + var withStackSave = (f) => { + var stack = stackSave(); + var ret = f(); + stackRestore(stack); + return ret; + }; + + + + 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();; +for (let i = 0; i < 32; ++i) tempFixedLengthArray.push(new Array(i));; +var miniTempWebGLFloatBuffersStorage = new Float32Array(288); + // Create GL_POOL_TEMP_BUFFERS_SIZE+1 temporary buffers, for uploads of size 0 through GL_POOL_TEMP_BUFFERS_SIZE inclusive + for (/**@suppress{duplicate}*/var i = 0; i <= 288; ++i) { + miniTempWebGLFloatBuffers[i] = miniTempWebGLFloatBuffersStorage.subarray(0, i); + }; +var miniTempWebGLIntBuffersStorage = new Int32Array(288); + // Create GL_POOL_TEMP_BUFFERS_SIZE+1 temporary buffers, for uploads of size 0 through GL_POOL_TEMP_BUFFERS_SIZE inclusive + for (/**@suppress{duplicate}*/var i = 0; i <= 288; ++i) { + miniTempWebGLIntBuffers[i] = miniTempWebGLIntBuffersStorage.subarray(0, i); + }; +// End JS library code + +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); +} +function slog_js_log(level,c_str) { const str = UTF8ToString(c_str); switch (level) { case 0: console.error(str); break; case 1: console.error(str); break; case 2: console.warn(str); break; default: console.info(str); break; } } +function sapp_js_add_beforeunload_listener() { Module.sokol_beforeunload = (event) => { if (__sapp_html5_get_ask_leave_site() != 0) { event.preventDefault(); event.returnValue = ' '; } }; window.addEventListener('beforeunload', Module.sokol_beforeunload); } +function sapp_js_remove_beforeunload_listener() { window.removeEventListener('beforeunload', Module.sokol_beforeunload); } +function sapp_js_add_clipboard_listener() { Module.sokol_paste = (event) => { const pasted_str = event.clipboardData.getData('text'); withStackSave(() => { const cstr = stringToUTF8OnStack(pasted_str); __sapp_emsc_onpaste(cstr); }); }; window.addEventListener('paste', Module.sokol_paste); } +function sapp_js_remove_clipboard_listener() { window.removeEventListener('paste', Module.sokol_paste); } +function sapp_js_write_clipboard(c_str) { const str = UTF8ToString(c_str); const ta = document.createElement('textarea'); ta.setAttribute('autocomplete', 'off'); ta.setAttribute('autocorrect', 'off'); ta.setAttribute('autocapitalize', 'off'); ta.setAttribute('spellcheck', 'false'); ta.style.left = -100 + 'px'; ta.style.top = -100 + 'px'; ta.style.height = 1; ta.style.width = 1; ta.value = str; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } +function sapp_js_add_dragndrop_listeners() { Module.sokol_drop_files = []; Module.sokol_dragenter = (event) => { event.stopPropagation(); event.preventDefault(); }; Module.sokol_dragleave = (event) => { event.stopPropagation(); event.preventDefault(); }; Module.sokol_dragover = (event) => { event.stopPropagation(); event.preventDefault(); }; Module.sokol_drop = (event) => { event.stopPropagation(); event.preventDefault(); const files = event.dataTransfer.files; Module.sokol_dropped_files = files; __sapp_emsc_begin_drop(files.length); for (let i = 0; i < files.length; i++) { withStackSave(() => { const cstr = stringToUTF8OnStack(files[i].name); __sapp_emsc_drop(i, cstr); }); } let mods = 0; if (event.shiftKey) { mods |= 1; } if (event.ctrlKey) { mods |= 2; } if (event.altKey) { mods |= 4; } if (event.metaKey) { mods |= 8; } __sapp_emsc_end_drop(event.clientX, event.clientY, mods); }; /** @suppress {missingProperties} */ const canvas = Module.sapp_emsc_target; canvas.addEventListener('dragenter', Module.sokol_dragenter, false); canvas.addEventListener('dragleave', Module.sokol_dragleave, false); canvas.addEventListener('dragover', Module.sokol_dragover, false); canvas.addEventListener('drop', Module.sokol_drop, false); } +function sapp_js_dropped_file_size(index) { /** @suppress {missingProperties} */ const files = Module.sokol_dropped_files; if ((index < 0) || (index >= files.length)) { return 0; } else { return files[index].size; } } +function sapp_js_fetch_dropped_file(index,callback,buf_ptr,buf_size,user_data) { const reader = new FileReader(); reader.onload = (loadEvent) => { const content = loadEvent.target.result; if (content.byteLength > buf_size) { __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); } else { HEAPU8.set(new Uint8Array(content), buf_ptr); __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); } }; reader.onerror = () => { __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); }; /** @suppress {missingProperties} */ const files = Module.sokol_dropped_files; reader.readAsArrayBuffer(files[index]); } +function sapp_js_remove_dragndrop_listeners() { /** @suppress {missingProperties} */ const canvas = Module.sapp_emsc_target; canvas.removeEventListener('dragenter', Module.sokol_dragenter); canvas.removeEventListener('dragleave', Module.sokol_dragleave); canvas.removeEventListener('dragover', Module.sokol_dragover); canvas.removeEventListener('drop', Module.sokol_drop); } +function sapp_js_init(c_str_target_selector,c_str_document_title) { if (c_str_document_title !== 0) { document.title = UTF8ToString(c_str_document_title); } const target_selector_str = UTF8ToString(c_str_target_selector); if (Module['canvas'] !== undefined) { if (typeof Module['canvas'] === 'object') { specialHTMLTargets[target_selector_str] = Module['canvas']; } else { console.warn("sokol_app.h: Module['canvas'] is set but is not an object"); } } Module.sapp_emsc_target = findCanvasEventTarget(target_selector_str); if (!Module.sapp_emsc_target) { console.warn("sokol_app.h: can't find html5_canvas_selector ", target_selector_str); } if (!Module.sapp_emsc_target.requestPointerLock) { console.warn("sokol_app.h: target doesn't support requestPointerLock: ", target_selector_str); } } +function sapp_js_request_pointerlock() { if (Module.sapp_emsc_target) { if (Module.sapp_emsc_target.requestPointerLock) { Module.sapp_emsc_target.requestPointerLock(); } } } +function sapp_js_exit_pointerlock() { if (document.exitPointerLock) { document.exitPointerLock(); } } +function sapp_js_set_cursor(cursor_type,shown) { if (Module.sapp_emsc_target) { let cursor; if (shown === 0) { cursor = "none"; } else switch (cursor_type) { case 0: cursor = "auto"; break; case 1: cursor = "default"; break; case 2: cursor = "text"; break; case 3: cursor = "crosshair"; break; case 4: cursor = "pointer"; break; case 5: cursor = "ew-resize"; break; case 6: cursor = "ns-resize"; break; case 7: cursor = "nwse-resize"; break; case 8: cursor = "nesw-resize"; break; case 9: cursor = "all-scroll"; break; case 10: cursor = "not-allowed"; break; default: cursor = "auto"; break; } Module.sapp_emsc_target.style.cursor = cursor; } } +function sapp_js_clear_favicon() { const link = document.getElementById('sokol-app-favicon'); if (link) { document.head.removeChild(link); } } +function sapp_js_set_favicon(w,h,pixels) { const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; const ctx = canvas.getContext('2d'); const img_data = ctx.createImageData(w, h); img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); ctx.putImageData(img_data, 0, 0); const new_link = document.createElement('link'); new_link.id = 'sokol-app-favicon'; new_link.rel = 'shortcut icon'; new_link.href = canvas.toDataURL(); document.head.appendChild(new_link); } +function sfetch_js_send_head_request(slot_id,path_cstr) { const path_str = UTF8ToString(path_cstr); fetch(path_str, { method: 'HEAD' }).then((response) => { if (response.ok) { const content_length = response.headers.get('Content-Length'); if (content_length === null) { console.warn(`sokol_fetch.h: HEAD ${path_str} response has no Content-Length`); __sfetch_emsc_failed_other(slot_id); } else { __sfetch_emsc_head_response(slot_id, Number(content_length)); } } else { __sfetch_emsc_failed_http_status(slot_id, response.status); } }).catch((err) => { console.error(`sokol_fetch.h: HEAD ${path_str} failed with: `, err); __sfetch_emsc_failed_other(slot_id); }); } +function sfetch_js_send_get_request(slot_id,path_cstr,offset,bytes_to_read,buf_ptr,buf_size) { const path_str = UTF8ToString(path_cstr); const headers = new Headers(); const range_request = bytes_to_read > 0; if (range_request) { headers.append('Range', `bytes=${offset}-${offset+bytes_to_read-1}`); } fetch(path_str, { method: 'GET', headers }).then((response) => { if (response.ok) { response.arrayBuffer().then((data) => { const u8_data = new Uint8Array(data); if (u8_data.length <= buf_size) { HEAPU8.set(u8_data, buf_ptr); __sfetch_emsc_get_response(slot_id, bytes_to_read, u8_data.length); } else { __sfetch_emsc_failed_buffer_too_small(slot_id); } }).catch((err) => { console.error(`sokol_fetch.h: GET ${path_str} failed with: `, err); __sfetch_emsc_failed_other(slot_id); }); } else { __sfetch_emsc_failed_http_status(slot_id, response.status); } }).catch((err) => { console.error(`sokol_fetch.h: GET ${path_str} failed with: `, err); __sfetch_emsc_failed_other(slot_id); }); } +var wasmImports = { + /** @export */ + __assert_fail: ___assert_fail, + /** @export */ + _abort_js: __abort_js, + /** @export */ + emscripten_cancel_main_loop: _emscripten_cancel_main_loop, + /** @export */ + emscripten_get_device_pixel_ratio: _emscripten_get_device_pixel_ratio, + /** @export */ + emscripten_get_element_css_size: _emscripten_get_element_css_size, + /** @export */ + emscripten_get_now: _emscripten_get_now, + /** @export */ + emscripten_performance_now: _emscripten_performance_now, + /** @export */ + emscripten_request_animation_frame_loop: _emscripten_request_animation_frame_loop, + /** @export */ + emscripten_resize_heap: _emscripten_resize_heap, + /** @export */ + emscripten_set_blur_callback_on_thread: _emscripten_set_blur_callback_on_thread, + /** @export */ + emscripten_set_canvas_element_size: _emscripten_set_canvas_element_size, + /** @export */ + emscripten_set_focus_callback_on_thread: _emscripten_set_focus_callback_on_thread, + /** @export */ + emscripten_set_keydown_callback_on_thread: _emscripten_set_keydown_callback_on_thread, + /** @export */ + emscripten_set_keypress_callback_on_thread: _emscripten_set_keypress_callback_on_thread, + /** @export */ + emscripten_set_keyup_callback_on_thread: _emscripten_set_keyup_callback_on_thread, + /** @export */ + emscripten_set_main_loop: _emscripten_set_main_loop, + /** @export */ + emscripten_set_mousedown_callback_on_thread: _emscripten_set_mousedown_callback_on_thread, + /** @export */ + emscripten_set_mouseenter_callback_on_thread: _emscripten_set_mouseenter_callback_on_thread, + /** @export */ + emscripten_set_mouseleave_callback_on_thread: _emscripten_set_mouseleave_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 */ + emscripten_set_pointerlockchange_callback_on_thread: _emscripten_set_pointerlockchange_callback_on_thread, + /** @export */ + emscripten_set_pointerlockerror_callback_on_thread: _emscripten_set_pointerlockerror_callback_on_thread, + /** @export */ + emscripten_set_resize_callback_on_thread: _emscripten_set_resize_callback_on_thread, + /** @export */ + emscripten_set_touchcancel_callback_on_thread: _emscripten_set_touchcancel_callback_on_thread, + /** @export */ + emscripten_set_touchend_callback_on_thread: _emscripten_set_touchend_callback_on_thread, + /** @export */ + emscripten_set_touchmove_callback_on_thread: _emscripten_set_touchmove_callback_on_thread, + /** @export */ + emscripten_set_touchstart_callback_on_thread: _emscripten_set_touchstart_callback_on_thread, + /** @export */ + emscripten_set_webglcontextlost_callback_on_thread: _emscripten_set_webglcontextlost_callback_on_thread, + /** @export */ + emscripten_set_webglcontextrestored_callback_on_thread: _emscripten_set_webglcontextrestored_callback_on_thread, + /** @export */ + emscripten_set_wheel_callback_on_thread: _emscripten_set_wheel_callback_on_thread, + /** @export */ + emscripten_webgl_create_context: _emscripten_webgl_create_context, + /** @export */ + emscripten_webgl_make_context_current: _emscripten_webgl_make_context_current, + /** @export */ + fd_close: _fd_close, + /** @export */ + fd_seek: _fd_seek, + /** @export */ + fd_write: _fd_write, + /** @export */ + glActiveTexture: _glActiveTexture, + /** @export */ + glAttachShader: _glAttachShader, + /** @export */ + glBindBuffer: _glBindBuffer, + /** @export */ + glBindBufferBase: _glBindBufferBase, + /** @export */ + glBindFramebuffer: _glBindFramebuffer, + /** @export */ + glBindRenderbuffer: _glBindRenderbuffer, + /** @export */ + glBindSampler: _glBindSampler, + /** @export */ + glBindTexture: _glBindTexture, + /** @export */ + glBindVertexArray: _glBindVertexArray, + /** @export */ + glBlendColor: _glBlendColor, + /** @export */ + glBlendEquationSeparate: _glBlendEquationSeparate, + /** @export */ + glBlendFuncSeparate: _glBlendFuncSeparate, + /** @export */ + glBlitFramebuffer: _glBlitFramebuffer, + /** @export */ + glBufferData: _glBufferData, + /** @export */ + glBufferSubData: _glBufferSubData, + /** @export */ + glClearBufferfi: _glClearBufferfi, + /** @export */ + glClearBufferfv: _glClearBufferfv, + /** @export */ + glClearBufferiv: _glClearBufferiv, + /** @export */ + glColorMask: _glColorMask, + /** @export */ + glCompileShader: _glCompileShader, + /** @export */ + glCompressedTexImage2D: _glCompressedTexImage2D, + /** @export */ + glCompressedTexImage3D: _glCompressedTexImage3D, + /** @export */ + glCreateProgram: _glCreateProgram, + /** @export */ + glCreateShader: _glCreateShader, + /** @export */ + glCullFace: _glCullFace, + /** @export */ + glDeleteBuffers: _glDeleteBuffers, + /** @export */ + glDeleteFramebuffers: _glDeleteFramebuffers, + /** @export */ + glDeleteProgram: _glDeleteProgram, + /** @export */ + glDeleteRenderbuffers: _glDeleteRenderbuffers, + /** @export */ + glDeleteSamplers: _glDeleteSamplers, + /** @export */ + glDeleteShader: _glDeleteShader, + /** @export */ + glDeleteTextures: _glDeleteTextures, + /** @export */ + glDeleteVertexArrays: _glDeleteVertexArrays, + /** @export */ + glDepthFunc: _glDepthFunc, + /** @export */ + glDepthMask: _glDepthMask, + /** @export */ + glDisable: _glDisable, + /** @export */ + glDisableVertexAttribArray: _glDisableVertexAttribArray, + /** @export */ + glDrawArrays: _glDrawArrays, + /** @export */ + glDrawArraysInstanced: _glDrawArraysInstanced, + /** @export */ + glDrawElements: _glDrawElements, + /** @export */ + glDrawElementsInstanced: _glDrawElementsInstanced, + /** @export */ + glEnable: _glEnable, + /** @export */ + glEnableVertexAttribArray: _glEnableVertexAttribArray, + /** @export */ + glFrontFace: _glFrontFace, + /** @export */ + glGenBuffers: _glGenBuffers, + /** @export */ + glGenRenderbuffers: _glGenRenderbuffers, + /** @export */ + glGenSamplers: _glGenSamplers, + /** @export */ + glGenTextures: _glGenTextures, + /** @export */ + glGenVertexArrays: _glGenVertexArrays, + /** @export */ + glGetAttribLocation: _glGetAttribLocation, + /** @export */ + glGetError: _glGetError, + /** @export */ + glGetIntegerv: _glGetIntegerv, + /** @export */ + glGetProgramInfoLog: _glGetProgramInfoLog, + /** @export */ + glGetProgramiv: _glGetProgramiv, + /** @export */ + glGetShaderInfoLog: _glGetShaderInfoLog, + /** @export */ + glGetShaderiv: _glGetShaderiv, + /** @export */ + glGetStringi: _glGetStringi, + /** @export */ + glGetUniformLocation: _glGetUniformLocation, + /** @export */ + glInvalidateFramebuffer: _glInvalidateFramebuffer, + /** @export */ + glLinkProgram: _glLinkProgram, + /** @export */ + glPixelStorei: _glPixelStorei, + /** @export */ + glPolygonOffset: _glPolygonOffset, + /** @export */ + glReadBuffer: _glReadBuffer, + /** @export */ + glRenderbufferStorageMultisample: _glRenderbufferStorageMultisample, + /** @export */ + glSamplerParameterf: _glSamplerParameterf, + /** @export */ + glSamplerParameteri: _glSamplerParameteri, + /** @export */ + glScissor: _glScissor, + /** @export */ + glShaderSource: _glShaderSource, + /** @export */ + glStencilFunc: _glStencilFunc, + /** @export */ + glStencilFuncSeparate: _glStencilFuncSeparate, + /** @export */ + glStencilMask: _glStencilMask, + /** @export */ + glStencilOp: _glStencilOp, + /** @export */ + glStencilOpSeparate: _glStencilOpSeparate, + /** @export */ + glTexImage2D: _glTexImage2D, + /** @export */ + glTexImage3D: _glTexImage3D, + /** @export */ + glTexParameteri: _glTexParameteri, + /** @export */ + glTexStorage2D: _glTexStorage2D, + /** @export */ + glTexStorage3D: _glTexStorage3D, + /** @export */ + glTexSubImage2D: _glTexSubImage2D, + /** @export */ + glTexSubImage3D: _glTexSubImage3D, + /** @export */ + glUniform1fv: _glUniform1fv, + /** @export */ + glUniform1i: _glUniform1i, + /** @export */ + glUniform1iv: _glUniform1iv, + /** @export */ + glUniform2fv: _glUniform2fv, + /** @export */ + glUniform2iv: _glUniform2iv, + /** @export */ + glUniform3fv: _glUniform3fv, + /** @export */ + glUniform3iv: _glUniform3iv, + /** @export */ + glUniform4fv: _glUniform4fv, + /** @export */ + glUniform4iv: _glUniform4iv, + /** @export */ + glUniformMatrix4fv: _glUniformMatrix4fv, + /** @export */ + glUseProgram: _glUseProgram, + /** @export */ + glVertexAttribDivisor: _glVertexAttribDivisor, + /** @export */ + glVertexAttribIPointer: _glVertexAttribIPointer, + /** @export */ + glVertexAttribPointer: _glVertexAttribPointer, + /** @export */ + glViewport: _glViewport, + /** @export */ + sapp_js_add_beforeunload_listener, + /** @export */ + sapp_js_add_clipboard_listener, + /** @export */ + sapp_js_add_dragndrop_listeners, + /** @export */ + sapp_js_clear_favicon, + /** @export */ + sapp_js_init, + /** @export */ + sapp_js_remove_beforeunload_listener, + /** @export */ + sapp_js_remove_clipboard_listener, + /** @export */ + sapp_js_remove_dragndrop_listeners, + /** @export */ + sapp_js_request_pointerlock, + /** @export */ + sapp_js_set_favicon, + /** @export */ + sfetch_js_send_get_request, + /** @export */ + sfetch_js_send_head_request, + /** @export */ + slog_js_log, + /** @export */ + wasm_debug_break: _wasm_debug_break, + /** @export */ + wasm_write_string: _wasm_write_string +}; +var wasmExports; +createWasm(); +var ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors', 0); +var _main = Module['_main'] = createExportWrapper('main', 2); +var _malloc = createExportWrapper('malloc', 1); +var __sapp_emsc_onpaste = Module['__sapp_emsc_onpaste'] = createExportWrapper('_sapp_emsc_onpaste', 1); +var __sapp_html5_get_ask_leave_site = Module['__sapp_html5_get_ask_leave_site'] = createExportWrapper('_sapp_html5_get_ask_leave_site', 0); +var __sapp_emsc_begin_drop = Module['__sapp_emsc_begin_drop'] = createExportWrapper('_sapp_emsc_begin_drop', 1); +var __sapp_emsc_drop = Module['__sapp_emsc_drop'] = createExportWrapper('_sapp_emsc_drop', 2); +var __sapp_emsc_end_drop = Module['__sapp_emsc_end_drop'] = createExportWrapper('_sapp_emsc_end_drop', 3); +var __sapp_emsc_invoke_fetch_cb = Module['__sapp_emsc_invoke_fetch_cb'] = createExportWrapper('_sapp_emsc_invoke_fetch_cb', 8); +var __sfetch_emsc_head_response = Module['__sfetch_emsc_head_response'] = createExportWrapper('_sfetch_emsc_head_response', 2); +var __sfetch_emsc_get_response = Module['__sfetch_emsc_get_response'] = createExportWrapper('_sfetch_emsc_get_response', 3); +var __sfetch_emsc_failed_http_status = Module['__sfetch_emsc_failed_http_status'] = createExportWrapper('_sfetch_emsc_failed_http_status', 2); +var __sfetch_emsc_failed_buffer_too_small = Module['__sfetch_emsc_failed_buffer_too_small'] = createExportWrapper('_sfetch_emsc_failed_buffer_too_small', 1); +var __sfetch_emsc_failed_other = Module['__sfetch_emsc_failed_other'] = createExportWrapper('_sfetch_emsc_failed_other', 1); +var _fflush = createExportWrapper('fflush', 1); +var _strerror = createExportWrapper('strerror', 1); +var _emscripten_stack_init = () => (_emscripten_stack_init = wasmExports['emscripten_stack_init'])(); +var _emscripten_stack_get_free = () => (_emscripten_stack_get_free = wasmExports['emscripten_stack_get_free'])(); +var _emscripten_stack_get_base = () => (_emscripten_stack_get_base = wasmExports['emscripten_stack_get_base'])(); +var _emscripten_stack_get_end = () => (_emscripten_stack_get_end = wasmExports['emscripten_stack_get_end'])(); +var __emscripten_stack_restore = (a0) => (__emscripten_stack_restore = wasmExports['_emscripten_stack_restore'])(a0); +var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc'])(a0); +var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports['emscripten_stack_get_current'])(); + +// Argument name here must shadow the `wasmExports` global so +// that it is recognised by metadce and minify-import-export-names +// passes. +function applySignatureConversions(wasmExports) { + // First, make a copy of the incoming exports object + wasmExports = Object.assign({}, wasmExports); + var makeWrapper___PP = (f) => (a0, a1, a2) => f(a0, BigInt(a1 ? a1 : 0), BigInt(a2 ? a2 : 0)); + var makeWrapper_pp = (f) => (a0) => Number(f(BigInt(a0))); + var makeWrapper__p = (f) => (a0) => f(BigInt(a0)); + var makeWrapper_p_ = (f) => (a0) => Number(f(a0)); + var makeWrapper_p = (f) => () => Number(f()); + + wasmExports['main'] = makeWrapper___PP(wasmExports['main']); + wasmExports['malloc'] = makeWrapper_pp(wasmExports['malloc']); + wasmExports['fflush'] = makeWrapper__p(wasmExports['fflush']); + wasmExports['strerror'] = makeWrapper_p_(wasmExports['strerror']); + wasmExports['emscripten_stack_get_base'] = makeWrapper_p(wasmExports['emscripten_stack_get_base']); + wasmExports['emscripten_stack_get_end'] = makeWrapper_p(wasmExports['emscripten_stack_get_end']); + wasmExports['_emscripten_stack_restore'] = makeWrapper__p(wasmExports['_emscripten_stack_restore']); + wasmExports['_emscripten_stack_alloc'] = makeWrapper_pp(wasmExports['_emscripten_stack_alloc']); + wasmExports['emscripten_stack_get_current'] = makeWrapper_p(wasmExports['emscripten_stack_get_current']); + return wasmExports; +} + +// include: postamble.js +// === Auto-generated postamble setup entry stuff === + +var missingLibrarySymbols = [ + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'getTempRet0', + 'setTempRet0', + 'zeroMemory', + 'getHeapMax', + 'growMemory', + 'strError', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'emscriptenLog', + 'readEmAsmArgs', + 'getExecutableName', + 'listenOnce', + 'autoResumeAudioContext', + 'getDynCaller', + 'dynCall', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'asmjsMangle', + 'asyncLoad', + 'alignMemory', + 'mmapAlloc', + 'HandleAllocator', + 'getNativeTypeSize', + 'addOnInit', + 'addOnPostCtor', + 'addOnPreMain', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'getCFunc', + 'ccall', + 'cwrap', + 'uleb128Encode', + 'sigToWasmTypes', + 'generateFuncType', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'reallyNegative', + 'unSign', + 'strLen', + 'reSign', + 'formatString', + 'intArrayFromString', + 'intArrayToString', + 'AsciiToString', + 'stringToAscii', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'writeArrayToMemory', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'battery', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'jsStackTrace', + 'getCallstack', + 'convertPCtoSourceLocation', + 'getEnvStrings', + 'checkWasiClock', + 'wasiRightsToMuslOFlags', + 'wasiOFlagsToMuslOFlags', + 'initRandomFill', + 'randomFill', + 'safeSetTimeout', + 'setImmediateWrapped', + 'safeRequestAnimationFrame', + 'clearImmediateWrapped', + 'registerPostMainLoop', + 'registerPreMainLoop', + 'getPromise', + 'makePromise', + 'idsToPromises', + 'makePromiseCallback', + 'ExceptionInfo', + 'findMatchingCatch', + 'Browser_asyncPrepareDataCounter', + 'isLeapYear', + 'ydayFromDate', + 'arraySum', + 'addDays', + 'getSocketFromFD', + 'getSocketAddress', + 'FS_createPreloadedFile', + 'FS_modeStringToFlags', + 'FS_getMode', + 'FS_stdin_getChar', + 'FS_unlink', + 'FS_createDataFile', + 'FS_mkdirTree', + '_setNetworkCallback', + 'emscriptenWebGLGetUniform', + 'emscriptenWebGLGetVertexAttrib', + '__glGetActiveAttribOrUniform', + 'writeGLArray', + 'runAndAbortIfError', + 'emscriptenWebGLGetIndexed', + 'ALLOC_NORMAL', + 'ALLOC_STACK', + 'allocate', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'demangle', + 'stackTrace', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + +var unexportedSymbols = [ + 'run', + 'addRunDependency', + 'removeRunDependency', + 'out', + 'err', + 'callMain', + 'abort', + 'wasmMemory', + 'wasmExports', + 'writeStackCookie', + 'checkStackCookie', + 'writeI53ToI64', + 'readI53FromI64', + 'readI53FromU64', + 'INT53_MAX', + 'INT53_MIN', + 'bigintToI53Checked', + 'stackSave', + 'stackRestore', + 'stackAlloc', + 'ptrToString', + 'exitJS', + 'abortOnCannotGrowMemory', + 'ENV', + 'ERRNO_CODES', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'readEmAsmArgsArray', + 'jstoi_q', + 'jstoi_s', + 'handleException', + 'keepRuntimeAlive', + 'callUserCallback', + 'maybeExit', + 'wasmTable', + 'noExitRuntime', + 'addOnPreRun', + 'addOnExit', + 'addOnPostRun', + 'freeTableIndexes', + 'functionsInTableMap', + 'setValue', + 'getValue', + 'PATH', + 'PATH_FS', + 'UTF8Decoder', + 'UTF8ArrayToString', + 'UTF8ToString', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'UTF16Decoder', + 'stringToNewUTF8', + 'stringToUTF8OnStack', + 'JSEvents', + 'registerKeyEventCallback', + 'specialHTMLTargets', + 'maybeCStringToJsString', + 'findEventTarget', + 'findCanvasEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'currentFullscreenStrategy', + 'restoreOldWindowedStyle', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'registerTouchEventCallback', + 'UNWIND_CACHE', + 'ExitStatus', + 'flush_NO_FILESYSTEM', + 'emSetImmediate', + 'emClearImmediate_deps', + 'emClearImmediate', + 'promiseMap', + 'uncaughtExceptionCount', + 'exceptionLast', + 'exceptionCaught', + 'Browser', + 'getPreloadedImageData__data', + 'wget', + 'MONTH_DAYS_REGULAR', + 'MONTH_DAYS_LEAP', + 'MONTH_DAYS_REGULAR_CUMULATIVE', + 'MONTH_DAYS_LEAP_CUMULATIVE', + 'SYSCALLS', + 'preloadPlugins', + 'FS_stdin_getChar_buffer', + 'FS_createPath', + 'FS_createDevice', + 'FS_readFile', + 'FS', + 'FS_createLazyFile', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'miniTempWebGLIntBuffers', + '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', + 'GL', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'colorChannelsInGlTextureFormat', + 'emscriptenWebGLGetTexPixelData', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'registerWebGlEventCallback', + 'AL', + 'GLUT', + 'EGL', + 'GLEW', + 'IDBStore', + 'SDL', + 'SDL_gfx', + 'webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance', + 'webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'print', + 'printErr', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + + + +var calledRun; + +function callMain() { + assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on Module["onRuntimeInitialized"])'); + assert(typeof onPreRuns === 'undefined' || onPreRuns.length == 0, 'cannot call main when preRun functions remain to be called'); + + var entryFunction = _main; + + var argc = 0; + var argv = 0; + + try { + + var ret = entryFunction(argc, BigInt(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(); +} + +function run() { + + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + + stackCheckInit(); + + preRun(); + + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + + function doRun() { + // run may have just been called through dependencies being fulfilled just in this very frame, + // or while the async setStatus time below was happening + assert(!calledRun); + calledRun = true; + Module['calledRun'] = true; + + if (ABORT) return; + + initRuntime(); + + preMain(); + + Module['onRuntimeInitialized']?.(); + consumedModuleProp('onRuntimeInitialized'); + + var noInitialRun = Module['noInitialRun'];legacyModuleProp('noInitialRun', 'noInitialRun'); + if (!noInitialRun) callMain(); + + postRun(); + } + + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(() => { + setTimeout(() => Module['setStatus'](''), 1); + doRun(); + }, 1); + } else + { + doRun(); + } + checkStackCookie(); +} + +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)'); + } +} + +if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].pop()(); + } +} +consumedModuleProp('preInit'); + +run(); + +// end include: postamble.js + diff --git a/dist/index.wasm b/dist/index.wasm new file mode 100755 index 0000000..6c1e6b9 Binary files /dev/null and b/dist/index.wasm differ diff --git a/dist/main.o b/dist/main.o new file mode 100644 index 0000000..877c9e6 Binary files /dev/null and b/dist/main.o differ diff --git a/dist/resources/DroidSerif-Regular.ttf b/dist/resources/DroidSerif-Regular.ttf new file mode 100644 index 0000000..239ba38 Binary files /dev/null and b/dist/resources/DroidSerif-Regular.ttf differ diff --git a/first b/first new file mode 100755 index 0000000..238d6c6 Binary files /dev/null and b/first differ diff --git a/first.jai b/first.jai new file mode 100644 index 0000000..2bc9a3e --- /dev/null +++ b/first.jai @@ -0,0 +1,113 @@ +#run { + { + process_result, output, error := run_command("bash", "./compile_shaders.sh", working_directory=tprint("%/src/shaders", #filepath)); + if process_result.exit_code != 0 { + log_error("Shader compilation failed."); + if output { + log_error(output,, logger = runtime_support_default_logger); + } + exit(1); + } + } + opt := get_build_options(); + args := opt.compile_time_command_line; + doWasmBuild := false; + for arg : args { + if arg == "wasm" then doWasmBuild = true; + } + if doWasmBuild { + set_build_options_dc(.{do_output = false}); + + make_directory_if_it_does_not_exist("dist", recursive = true); + + + { + + process_result, output, error := run_command("cp", "-r", "./resources", "./dist/", working_directory=tprint("%", #filepath)); + + + w := compiler_create_workspace("Wasm"); + + options := get_build_options(w); + copy_commonly_propagated_fields(get_build_options(), *options); + + options.output_type = .OBJECT_FILE; + options.backend = .LLVM; // WASM only works with the LLVM backend, obviously. + options.os_target = .WASM; + options.cpu_target = .CUSTOM; + options.emit_debug_info = .DWARF; + options.backtrace_on_crash = .OFF; // Runtime_Support_Crash_Handler doesn’t support WASM (yet?) + options.output_path = "dist/"; + options.output_executable_name = "main"; + options.llvm_options.target_system_features = "+bulk-memory"; // "This options is needed so that "memcpy" and "memset" are mapped to "memory.copy" and "memory.fill" instructions in WASM. + options.llvm_options.enable_split_modules = false; + options.llvm_options.function_sections = true; // To get around "LLVM ERROR: section already has a defining function: .text" + + import_paths: [..]string; + // Add our own modules folder first so that we can override modules with our own version, if necessary. + for options.import_path array_add(*import_paths, it); + options.import_path = import_paths; + + // This was compiled from https://github.com/wingo/walloc via "clang -Oz --target=wasm64 -nostdlib -c -o walloc.o walloc.c". + // We should probably port this allocator to Jai instead… + // -rluba, 2023-11-15 + walloc_object_file_path := "walloc.o"; + + STACK_SIZE :: 24 * 1024; + options.additional_linker_arguments = .["--stack-first", "-z", tprint("stack-size=%", STACK_SIZE), walloc_object_file_path]; + + set_build_options(options, w); + + // Replace the default allocator with Walloc (https://github.com/wingo/walloc). + remap_import(w, "*", "Default_Allocator", "Walloc"); + + compiler_begin_intercept(w); + + add_build_file("src/platform_specific/main_web.jai", w); + + while true { + message := compiler_wait_for_message(); + if message.kind == { + case .TYPECHECKED; + typechecked := cast(*Message_Typechecked) message; + for body: typechecked.procedure_bodies { + header := body.expression.header; + // You could replace individual procedure bodies here, if you needed to. + } + + case .COMPLETE; + break; + } + } + + compiler_end_intercept(w); + } + + { + args := string.[ + "emcc", + "src/platform_specific/main.c", "dist/main.o", "modules/sokol-jai/sokol/gfx/sokol_gfx_wasm_gl_debug.a", "modules/sokol-jai/sokol/log/sokol_log_wasm_gl_debug.a", "modules/sokol-jai/sokol/time/sokol_time_wasm_gl_debug.a", "modules/sokol-jai/sokol/app/sokol_app_wasm_gl_debug.a", "modules/sokol-jai/sokol/glue/sokol_glue_wasm_gl_debug.a", "modules/sokol-jai/sokol/fetch/sokol_fetch_wasm_gl_debug.a", "modules/sokol-jai/sokol/gl/sokol_gl_wasm_gl_debug.a", "modules/sokol-jai/sokol/fontstash/sokol_fontstash_wasm_gl_debug.a", + "-o", "dist/index.html", + "-sERROR_ON_UNDEFINED_SYMBOLS=1", "-sMEMORY64", "-sMAX_WEBGL_VERSION=2", + "--js-library=src/platform_specific/runtime.js", + "--shell-file=src/platform_specific/shell.html", + ]; + process_result, output, error := run_command(..args, capture_and_return_output = true); + if process_result.exit_code != 0 { + log_error("EMCC compilation failed."); + if error { + log_error(error,, logger = runtime_support_default_logger); + } + exit(1); + } + } + } else { + #load "src/platform_specific/main_native.jai"; + } +} + +#import "Basic"; +#import "Compiler"; +#import "Process"; +#import "File"; + diff --git a/modules/Walloc.jai b/modules/Walloc.jai new file mode 100755 index 0000000..62003ea --- /dev/null +++ b/modules/Walloc.jai @@ -0,0 +1,63 @@ +#scope_module + +walloc_malloc :: (size: s64) -> *void #foreign "malloc"; // Will be provided by WASM +walloc_free :: (p: *void) #foreign "free"; // Will be provided by WASM + +#scope_export + +walloc_allocator :: Allocator.{allocator_proc, null}; + +allocator_proc :: (mode: Allocator_Mode, requested_size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void { + if #complete mode == { + case .STARTUP; #through; + case .SHUTDOWN; #through; + case .THREAD_START; #through; + case .THREAD_STOP; + return null; + + case .ALLOCATE; #through; + case .RESIZE; + // @TODO: Can we support proper realloc? + result := walloc_malloc(requested_size); + if mode == .RESIZE { + size_to_copy := min(old_size, requested_size); + if result && size_to_copy memcpy(result, old_memory, size_to_copy); + } + return result; + + case .FREE; + log("[Walloc]: FREE"); + walloc_free(old_memory); + return null; + + case .CREATE_HEAP; #through; + case .DESTROY_HEAP; + context.handling_assertion_failure = true; + context.assertion_failed(#location(), "This allocator does not support multiple heaps.\n"); + context.handling_assertion_failure = false; + return null; + + + case .IS_THIS_YOURS; + context.handling_assertion_failure = true; + context.assertion_failed(#location(), "This allocator does not support IS_THIS_YOURS.\n"); + context.handling_assertion_failure = false; + return null; + + case .CAPS; + if old_memory { < + +Auto-generated Jai bindings for the [sokol headers](https://github.com/floooh/sokol). + +To include sokol in your project you can copy the [sokol](sokol/) directory. + +## BUILD + +Supported platforms are: Windows, macOS, Linux (with X11) + +On Linux install the following packages: libglu1-mesa-dev, mesa-common-dev, xorg-dev, libasound-dev +(or generally: the dev packages required for X11, GL and ALSA development) + +1. First build the required static link libraries: + + ```bash + cd sokol + # on macOS: + ./build_clibs_macos.sh + # on Linux: + ./build_clibs_linux.sh + # on Windows with MSVC (from a 'Visual Studio Developer Command Prompt') + build_clibs_windows.cmd + cd .. + ``` + +2. Create a build directory and cd into it: + ```bash + mkdir build + cd build + ``` + +3. Build and run the samples: + ```bash + jai ../examples/first.jai - clear + jai ../examples/first.jai - triangle + jai ../examples/first.jai - offscreen + jai ../examples/first.jai - blend + jai ../examples/first.jai - debugtext-print + jai ../examples/first.jai - saudio + jai ../examples/first.jai - fontstash-sapp + jai ../examples/first.jai - sgl-context-sapp + ``` + + By default, the backend 3D API will be selected based on the target platform: + + - macOS: Metal + - Windows: D3D11 + - Linux: GL + + To force the GL backend on macOS or Windows, build with ```-GL```: + + ``` + jai ../examples/first.jai - clear -GL + ``` + + The ```clear``` sample prints the selected backend to the terminal: + + ``` + jai ../examples/first.jai - clear -GL + >> using GL backend + ``` diff --git a/modules/sokol-jai/examples/.gitignore b/modules/sokol-jai/examples/.gitignore new file mode 100644 index 0000000..e379999 --- /dev/null +++ b/modules/sokol-jai/examples/.gitignore @@ -0,0 +1,21 @@ +.build +clear/clear +triangle/triangle +quad/quad +bufferoffsets/bufferoffsets +cube/cube +noninterleaved/noninterleaved +texcube/texcube +shapes/shapes +offscreen/offscreen +instancing/instancing +mrt/mrt +blend/blend +debugtext/debugtext +debugtext-print/debugtext-print +debugtext-userfont/debugtext-userfont +saudio/saudio +sgl/sgl +sgl-points/sgl-points +sgl-context/sgl-context +vertexpull/vertexpull diff --git a/modules/sokol-jai/examples/blend/module.jai b/modules/sokol-jai/examples/blend/module.jai new file mode 100644 index 0000000..c9ec2a8 --- /dev/null +++ b/modules/sokol-jai/examples/blend/module.jai @@ -0,0 +1,138 @@ +//------------------------------------------------------------------------------ +// blend/module.jai +// +// Test/demonstrate blend modes. +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#load "../math.jai"; +#load "./shader.jai"; + +NUM_BLEND_FACTORS :: 15; + +state: struct { + pass_action: sg_pass_action; + bind: sg_bindings; + pips: [NUM_BLEND_FACTORS][NUM_BLEND_FACTORS]sg_pipeline; + bg_pip: sg_pipeline; + r: float; + quad_vs_params: Quad_Vs_Params; + bg_fs_params: Bg_Fs_Params; +} + +init :: () #c_call { + push_context,defer_pop; + sg_setup(*(sg_desc.{ + pipeline_pool_size = NUM_BLEND_FACTORS * NUM_BLEND_FACTORS + 1, + environment = xx,force sglue_environment(), + logger = .{ func = slog_func }, + })); + + // a quad vertex buffer + vertices := float.[ + // pos color + -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.5, + +1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 0.5, + -1.0, +1.0, 0.0, 0.0, 0.0, 1.0, 0.5, + +1.0, +1.0, 0.0, 1.0, 1.0, 0.0, 0.5, + ]; + buffer := sg_buffer_desc.{ data = .{ ptr = *vertices, size = size_of(type_of(vertices)) } }; + state.bind.vertex_buffers[0] = sg_make_buffer(*buffer); + + // shader and pipeline object for rendering the background quad + bg_pip_desc: sg_pipeline_desc; + bg_pip_desc.shader = sg_make_shader(*bg_shader_desc(sg_query_backend())); + // we use the same vertex buffer as for the colored 3D quads, + // but only the first two floats from the position, need to + // provide a stride to skip the gap to the next vertex + bg_pip_desc.layout.buffers[0] = .{ stride = 28 }; + bg_pip_desc.layout.attrs[ATTR_bg_position] = .{ format = .FLOAT2 }; + bg_pip_desc.primitive_type = .TRIANGLE_STRIP; + state.bg_pip = sg_make_pipeline(*bg_pip_desc); + + // a shader for the blended quads + quad_shd := sg_make_shader(*quad_shader_desc(sg_query_backend())); + + // one pipeline object per blend-factor combination + pip_desc := sg_pipeline_desc.{ + shader = quad_shd, + primitive_type = .TRIANGLE_STRIP, + blend_color = .{ 1.0, 0.0, 0.0, 1.0 }, + }; + pip_desc.layout.attrs[ATTR_quad_position] = .{ format = .FLOAT3 }; + pip_desc.layout.attrs[ATTR_quad_color0] = .{ format = .FLOAT4 }; + for src : 0 .. NUM_BLEND_FACTORS-1 { + for dst : 0 .. NUM_BLEND_FACTORS-1 { + pip_desc.colors[0].blend = .{ + enabled = true, + src_factor_rgb = cast(sg_blend_factor) (src + 1), + dst_factor_rgb = cast(sg_blend_factor) (dst + 1), + src_factor_alpha = .ONE, + dst_factor_alpha = .ZERO, + }; + state.pips[src][dst] = sg_make_pipeline(*pip_desc); + } + } +} + +frame :: () #c_call { + push_context,defer_pop; + t := cast(float) (sapp_frame_duration() * 60.0); + state.r += 0.6 * t; + state.bg_fs_params.tick += 1.0 * t; + + // view-projection matrix + proj := persp_mat4(fov = 90.0, aspect = sapp_widthf() / sapp_heightf(), near = 0.01, far = 100.0); + view := lookat_mat4(eye = .{ 0.0, 0.0, 25.0 }, center = .{}, up = .{ 0.0, 1.0, 0.0 }); + view_proj := multiply_mat4(proj, view); + + // start rendering + sg_begin_pass(*(sg_pass.{ action = state.pass_action, swapchain = xx,force sglue_swapchain() })); + + // draw a background quad + sg_apply_pipeline(state.bg_pip); + sg_apply_bindings(*state.bind); + sg_apply_uniforms(UB_bg_fs_params, *(sg_range.{ ptr = *state.bg_fs_params, size = size_of(Bg_Fs_Params) })); + sg_draw(0, 4, 1); + + // draw the blended quads + r0 := state.r; + for src : 0 .. NUM_BLEND_FACTORS - 1 { + for dst : 0 .. NUM_BLEND_FACTORS - 1 { + // compute model-view-proj matrix + rm := rotate_mat4(r0, .{ 0.0, 1.0, 0.0 }); + x := cast(float) (dst - NUM_BLEND_FACTORS/2) * 3.0; + y := cast(float) (src - NUM_BLEND_FACTORS/2) * 2.2; + model := multiply_mat4(translate_mat4(.{ x, y, 0.0 }), rm); + state.quad_vs_params.mvp = multiply_mat4(view_proj, model); + + sg_apply_pipeline(state.pips[src][dst]); + sg_apply_bindings(*state.bind); + sg_apply_uniforms(UB_quad_vs_params, *(sg_range.{ ptr = *state.quad_vs_params, size = size_of(Quad_Vs_Params) })); + sg_draw(0, 4, 1); + } + } + sg_end_pass(); + sg_commit(); +} + +cleanup :: () #c_call { + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 800, + height = 600, + sample_count = 4, + window_title = "blend", + icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} diff --git a/modules/sokol-jai/examples/blend/shader.glsl b/modules/sokol-jai/examples/blend/shader.glsl new file mode 100644 index 0000000..26a13f9 --- /dev/null +++ b/modules/sokol-jai/examples/blend/shader.glsl @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +// shaders for blend-sapp sample +//------------------------------------------------------------------------------ +@ctype mat4 Matrix4 + +@vs vs_bg +in vec2 position; +void main() { + gl_Position = vec4(position, 0.5, 1.0); +} +@end + +@fs fs_bg +layout(binding=0) uniform bg_fs_params { + float tick; +}; + +out vec4 frag_color; + +void main() { + vec2 xy = fract((gl_FragCoord.xy-vec2(tick)) / 50.0); + frag_color = vec4(vec3(xy.x*xy.y), 1.0); +} +@end + +@program bg vs_bg fs_bg + +@vs vs_quad +layout(binding=0) uniform quad_vs_params { + mat4 mvp; +}; + +in vec4 position; +in vec4 color0; + +out vec4 color; + +void main() { + gl_Position = mvp * position; + color = color0; +} +@end + +@fs fs_quad +in vec4 color; +out vec4 frag_color; +void main() { + frag_color = color; +} +@end + +@program quad vs_quad fs_quad diff --git a/modules/sokol-jai/examples/blend/shader.jai b/modules/sokol-jai/examples/blend/shader.jai new file mode 100644 index 0000000..a53ceff --- /dev/null +++ b/modules/sokol-jai/examples/blend/shader.jai @@ -0,0 +1,758 @@ +/* + #version:1# (machine generated, don't edit!) + + Generated by sokol-shdc (https://github.com/floooh/sokol-tools) + + Cmdline: + sokol-shdc -i shader.glsl -o shader.jai -l glsl410:metal_macos:hlsl5 -f sokol_jai + + Overview: + ========= + Shader program: 'bg': + Get shader desc: bg_shader_desc(sg_query_backend()) + Vertex Shader: vs_bg + Fragment Shader: fs_bg + Attributes: + ATTR_bg_position => 0 + Shader program: 'quad': + Get shader desc: quad_shader_desc(sg_query_backend()) + Vertex Shader: vs_quad + Fragment Shader: fs_quad + Attributes: + ATTR_quad_position => 0 + ATTR_quad_color0 => 1 + Bindings: + Uniform block 'bg_fs_params': + Jai struct: Bg_Fs_Params + Bind slot: UB_bg_fs_params => 0 + Uniform block 'quad_vs_params': + Jai struct: Quad_Vs_Params + Bind slot: UB_quad_vs_params => 0 +*/ +ATTR_bg_position :: 0; +ATTR_quad_position :: 0; +ATTR_quad_color0 :: 1; +UB_bg_fs_params :: 0; +UB_quad_vs_params :: 0; +Bg_Fs_Params :: struct { + tick: float; + _: [12]u8; +}; +Quad_Vs_Params :: struct { + mvp: Matrix4; +}; +/* + #version 410 + + layout(location = 0) in vec2 position; + + void main() + { + gl_Position = vec4(position, 0.5, 1.0); + } + +*/ +vs_bg_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28, + 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x2c,0x20,0x30,0x2e,0x35,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +]; +/* + #version 410 + + uniform vec4 bg_fs_params[1]; + layout(location = 0) out vec4 frag_color; + + void main() + { + vec2 _28 = fract((gl_FragCoord.xy - vec2(bg_fs_params[0].x)) * vec2(0.0199999995529651641845703125)); + float _39 = _28.x * _28.y; + frag_color = vec4(_39, _39, _39, 1.0); + } + +*/ +fs_bg_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x62,0x67,0x5f,0x66,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x5f,0x32, + 0x38,0x20,0x3d,0x20,0x66,0x72,0x61,0x63,0x74,0x28,0x28,0x67,0x6c,0x5f,0x46,0x72, + 0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x20,0x2d,0x20,0x76,0x65,0x63, + 0x32,0x28,0x62,0x67,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30, + 0x5d,0x2e,0x78,0x29,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x30, + 0x31,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x35,0x35,0x32,0x39,0x36,0x35,0x31,0x36, + 0x34,0x31,0x38,0x34,0x35,0x37,0x30,0x33,0x31,0x32,0x35,0x29,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x33,0x39,0x20,0x3d,0x20,0x5f, + 0x32,0x38,0x2e,0x78,0x20,0x2a,0x20,0x5f,0x32,0x38,0x2e,0x79,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76, + 0x65,0x63,0x34,0x28,0x5f,0x33,0x39,0x2c,0x20,0x5f,0x33,0x39,0x2c,0x20,0x5f,0x33, + 0x39,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 410 + + uniform vec4 quad_vs_params[4]; + layout(location = 0) in vec4 position; + layout(location = 0) out vec4 color; + layout(location = 1) in vec4 color0; + + void main() + { + gl_Position = mat4(quad_vs_params[0], quad_vs_params[1], quad_vs_params[2], quad_vs_params[3]) * position; + color = color0; + } + +*/ +vs_quad_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x71,0x75,0x61,0x64,0x5f, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69, + 0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a, + 0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x6d,0x61,0x74,0x34,0x28,0x71,0x75,0x61,0x64,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72, + 0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x71,0x75,0x61,0x64,0x5f,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x71,0x75,0x61,0x64,0x5f, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x71,0x75, + 0x61,0x64,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29, + 0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b, + 0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 410 + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 color; + + void main() + { + frag_color = color; + } + +*/ +fs_quad_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20, + 0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69, + 0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + static float4 gl_Position; + static float2 position; + + struct SPIRV_Cross_Input + { + float2 position : TEXCOORD0; + }; + + struct SPIRV_Cross_Output + { + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = float4(position, 0.5f, 1.0f); + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + return stage_output; + } +*/ +vs_bg_source_hlsl5 := u8.[ + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56, + 0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56, + 0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x76, + 0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20, + 0x30,0x2e,0x35,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70, + 0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x70,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e, + 0x70,0x75,0x74,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f, + 0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70, + 0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75, + 0x74,0x70,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +]; +/* + cbuffer bg_fs_params : register(b0) + { + float _17_tick : packoffset(c0); + }; + + + static float4 gl_FragCoord; + static float4 frag_color; + + struct SPIRV_Cross_Input + { + float4 gl_FragCoord : SV_Position; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + float2 _28 = frac((gl_FragCoord.xy - _17_tick.xx) * 0.0199999995529651641845703125f.xx); + float _39 = _28.x * _28.y; + frag_color = float4(_39, _39, _39, 1.0f); + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + gl_FragCoord = stage_input.gl_FragCoord; + gl_FragCoord.w = 1.0 / gl_FragCoord.w; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +fs_bg_source_hlsl5 := u8.[ + 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x62,0x67,0x5f,0x66,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28, + 0x62,0x30,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20, + 0x5f,0x31,0x37,0x5f,0x74,0x69,0x63,0x6b,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f, + 0x66,0x66,0x73,0x65,0x74,0x28,0x63,0x30,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x0a, + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x46,0x72,0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x73,0x74,0x61,0x74, + 0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50, + 0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a, + 0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f, + 0x46,0x72,0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x20,0x3a,0x20,0x53,0x56,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72, + 0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f, + 0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a, + 0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28, + 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f, + 0x32,0x38,0x20,0x3d,0x20,0x66,0x72,0x61,0x63,0x28,0x28,0x67,0x6c,0x5f,0x46,0x72, + 0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x2e,0x78,0x79,0x20,0x2d,0x20,0x5f,0x31,0x37, + 0x5f,0x74,0x69,0x63,0x6b,0x2e,0x78,0x78,0x29,0x20,0x2a,0x20,0x30,0x2e,0x30,0x31, + 0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x35,0x35,0x32,0x39,0x36,0x35,0x31,0x36,0x34, + 0x31,0x38,0x34,0x35,0x37,0x30,0x33,0x31,0x32,0x35,0x66,0x2e,0x78,0x78,0x29,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x33,0x39,0x20,0x3d, + 0x20,0x5f,0x32,0x38,0x2e,0x78,0x20,0x2a,0x20,0x5f,0x32,0x38,0x2e,0x79,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x33,0x39,0x2c,0x20,0x5f,0x33,0x39, + 0x2c,0x20,0x5f,0x33,0x39,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74, + 0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43, + 0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c, + 0x5f,0x46,0x72,0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x20,0x3d,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x46,0x72,0x61,0x67, + 0x43,0x6f,0x6f,0x72,0x64,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x46,0x72, + 0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x2e,0x77,0x20,0x3d,0x20,0x31,0x2e,0x30,0x20, + 0x2f,0x20,0x67,0x6c,0x5f,0x46,0x72,0x61,0x67,0x43,0x6f,0x6f,0x72,0x64,0x2e,0x77, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28, + 0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +]; +/* + cbuffer quad_vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + }; + + + static float4 gl_Position; + static float4 position; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float4 color0 : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 color : TEXCOORD0; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.color = color; + return stage_output; + } +*/ +vs_quad_source_hlsl5 := u8.[ + 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x71,0x75,0x61,0x64,0x5f,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65, + 0x72,0x28,0x62,0x30,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x72,0x6f,0x77,0x5f, + 0x6d,0x61,0x6a,0x6f,0x72,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x5f, + 0x31,0x39,0x5f,0x6d,0x76,0x70,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66, + 0x73,0x65,0x74,0x28,0x63,0x30,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x0a,0x73,0x74, + 0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b, + 0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x73,0x74,0x72, + 0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f, + 0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x54,0x45, + 0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x54,0x45,0x58, + 0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75, + 0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f, + 0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f, + 0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x53, + 0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a, + 0x76,0x6f,0x69,0x64,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x75,0x6c,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x2c,0x20,0x5f,0x31,0x39,0x5f,0x6d,0x76,0x70,0x29,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b, + 0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f, + 0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52, + 0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74, + 0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d,0x20,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e, + 0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f, + 0x75,0x74,0x70,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a, + 0x00, +]; +/* + static float4 frag_color; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 color : TEXCOORD0; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +fs_quad_source_hlsl5 := u8.[ + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a, + 0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45, + 0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72, + 0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f, + 0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a, + 0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28, + 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69, + 0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52, + 0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72, + 0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float2 position [[attribute(0)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(in.position, 0.5, 1.0); + return out; + } + +*/ +vs_bg_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30, + 0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72, + 0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a, + 0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74, + 0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20, + 0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74, + 0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f, + 0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x2c,0x20,0x30,0x2e,0x35,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct bg_fs_params + { + float tick; + }; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + fragment main0_out main0(constant bg_fs_params& _17 [[buffer(0)]], float4 gl_FragCoord [[position]]) + { + main0_out out = {}; + float2 _28 = fract((gl_FragCoord.xy - float2(_17.tick)) * float2(0.0199999995529651641845703125)); + float _39 = _28.x * _28.y; + out.frag_color = float4(_39, _39, _39, 1.0); + return out; + } + +*/ +fs_bg_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x62, + 0x67,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x74,0x69,0x63,0x6b,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x63,0x6f,0x6c, + 0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61, + 0x67,0x6d,0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x62, + 0x67,0x5f,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x31,0x37, + 0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x46,0x72,0x61,0x67,0x43,0x6f, + 0x6f,0x72,0x64,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d, + 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75, + 0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x32,0x38,0x20,0x3d,0x20,0x66,0x72,0x61, + 0x63,0x74,0x28,0x28,0x67,0x6c,0x5f,0x46,0x72,0x61,0x67,0x43,0x6f,0x6f,0x72,0x64, + 0x2e,0x78,0x79,0x20,0x2d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x5f,0x31,0x37, + 0x2e,0x74,0x69,0x63,0x6b,0x29,0x29,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, + 0x28,0x30,0x2e,0x30,0x31,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x35,0x35,0x32,0x39, + 0x36,0x35,0x31,0x36,0x34,0x31,0x38,0x34,0x35,0x37,0x30,0x33,0x31,0x32,0x35,0x29, + 0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x33,0x39, + 0x20,0x3d,0x20,0x5f,0x32,0x38,0x2e,0x78,0x20,0x2a,0x20,0x5f,0x32,0x38,0x2e,0x79, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x33, + 0x39,0x2c,0x20,0x5f,0x33,0x39,0x2c,0x20,0x5f,0x33,0x39,0x2c,0x20,0x31,0x2e,0x30, + 0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75, + 0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct quad_vs_params + { + float4x4 mvp; + }; + + struct main0_out + { + float4 color [[user(locn0)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float4 color0 [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant quad_vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.color = in.color0; + return out; + } + +*/ +vs_quad_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x71, + 0x75,0x61,0x64,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72, + 0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30, + 0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72, + 0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b, + 0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a, + 0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30, + 0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30, + 0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69, + 0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x71,0x75, + 0x61,0x64,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x31, + 0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74, + 0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f, + 0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75, + 0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 color [[user(locn0)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.frag_color = in.color; + return out; + } + +*/ +fs_quad_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63, + 0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d, + 0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61, + 0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20, + 0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72, + 0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +bg_shader_desc :: (backend: sg_backend) -> sg_shader_desc { + desc: sg_shader_desc; + desc.label = "bg_shader"; + if backend == { + case .GLCORE; + desc.vertex_func.source = xx *vs_bg_source_glsl410; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_bg_source_glsl410; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "position"; + desc.uniform_blocks[0].stage = .FRAGMENT; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 16; + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4; + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 1; + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "bg_fs_params"; + case .D3D11; + desc.vertex_func.source = xx *vs_bg_source_hlsl5; + desc.vertex_func.d3d11_target = "vs_5_0"; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_bg_source_hlsl5; + desc.fragment_func.d3d11_target = "ps_5_0"; + desc.fragment_func.entry = "main"; + desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + desc.attrs[0].hlsl_sem_index = 0; + desc.uniform_blocks[0].stage = .FRAGMENT; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 16; + desc.uniform_blocks[0].hlsl_register_b_n = 0; + case .METAL_MACOS; + desc.vertex_func.source = xx *vs_bg_source_metal_macos; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = xx *fs_bg_source_metal_macos; + desc.fragment_func.entry = "main0"; + desc.uniform_blocks[0].stage = .FRAGMENT; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 16; + desc.uniform_blocks[0].msl_buffer_n = 0; + } + return desc; +} +quad_shader_desc :: (backend: sg_backend) -> sg_shader_desc { + desc: sg_shader_desc; + desc.label = "quad_shader"; + if backend == { + case .GLCORE; + desc.vertex_func.source = xx *vs_quad_source_glsl410; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_quad_source_glsl410; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "position"; + desc.attrs[1].glsl_name = "color0"; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4; + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 4; + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "quad_vs_params"; + case .D3D11; + desc.vertex_func.source = xx *vs_quad_source_hlsl5; + desc.vertex_func.d3d11_target = "vs_5_0"; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_quad_source_hlsl5; + desc.fragment_func.d3d11_target = "ps_5_0"; + desc.fragment_func.entry = "main"; + desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + desc.attrs[0].hlsl_sem_index = 0; + desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + desc.attrs[1].hlsl_sem_index = 1; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].hlsl_register_b_n = 0; + case .METAL_MACOS; + desc.vertex_func.source = xx *vs_quad_source_metal_macos; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = xx *fs_quad_source_metal_macos; + desc.fragment_func.entry = "main0"; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].msl_buffer_n = 0; + } + return desc; +} diff --git a/modules/sokol-jai/examples/clear/module.jai b/modules/sokol-jai/examples/clear/module.jai new file mode 100644 index 0000000..221a443 --- /dev/null +++ b/modules/sokol-jai/examples/clear/module.jai @@ -0,0 +1,59 @@ +//------------------------------------------------------------------------------ +// clear/module.jai +// +// Minimal sample which just clears the default framebuffer +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); + +pass_action: sg_pass_action; + +init :: () #c_call { + push_context,defer_pop; + + sg_setup(*(sg_desc.{ + environment = xx,force sglue_environment(), + logger = .{ func = slog_func }, + })); + pass_action.colors[0] = .{ load_action = .CLEAR, clear_value = .{ 1.0, 0.0, 0.0, 1.0 } }; + + // just some debug output what backend we're running on + if sg_query_backend() == { + case .D3D11; print(">> using D3D11 backend"); + case .GLCORE; #through; + case .GLES3; print(">> using GL backend"); + case .METAL_MACOS; #through; + case .METAL_IOS; #through; + case .METAL_SIMULATOR; print(">> using Metal backend"); + case .WGPU; print(">> using WebGPU backend"); + case .DUMMY; print(">> using dummy backend"); + } +} + +frame :: () #c_call { + g := pass_action.colors[0].clear_value.g + 0.01; + pass_action.colors[0].clear_value.g = ifx(g > 1.0) then 0.0 else g; + sg_begin_pass(*(sg_pass.{ action = pass_action, swapchain = xx,force sglue_swapchain() })); + sg_end_pass(); + sg_commit(); +} + +cleanup :: () #c_call { + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 400, + height = 300, + window_title = "clear", + icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} diff --git a/modules/sokol-jai/examples/debugtext-print/module.jai b/modules/sokol-jai/examples/debugtext-print/module.jai new file mode 100644 index 0000000..4cf5488 --- /dev/null +++ b/modules/sokol-jai/examples/debugtext-print/module.jai @@ -0,0 +1,92 @@ +//------------------------------------------------------------------------------ +// debugtext-print/module.jai +// +// Simple text rendering with sokol/debugtext, formatting, tabs, etc... +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#import,dir "../../sokol/debugtext"(USE_GL=USE_GL); + +NUM_FONTS :: 3; + +Color :: struct { + r, g, b: u8; +} + +state: struct { + pass_action: sg_pass_action; + palette: [3]Color; +} = .{ + pass_action = .{ + colors = .[ + .{ load_action = .CLEAR, clear_value = .{ 0.0, 0.125, 0.25, 1.0 } }, + .{}, .{}, .{}, + ], + }, + palette = .[ + .{ 0xf4, 0x43, 0x36 }, + .{ 0x21, 0x96, 0xf3 }, + .{ 0x4c, 0xaf, 0x50 }, + ], +}; + +init :: () #c_call { + sg_setup(*(sg_desc.{ + environment = xx,force sglue_environment(), + logger = .{ func = slog_func }, + })); + + sdtx_setup(*(sdtx_desc_t.{ + fonts[0] = sdtx_font_kc854(), + fonts[1] = sdtx_font_c64(), + fonts[2] = sdtx_font_oric(), + logger = .{ func = slog_func }, + })); +} + +frame :: () #c_call { + push_context,defer_pop; + + frame_count := cast(u32) sapp_frame_count(); + frame_time := sapp_frame_duration() * 1000.0; + + sdtx_canvas(sapp_widthf() * 0.5, sapp_heightf() * 0.5); + sdtx_origin(3.0, 3.0); + for i : 0 .. NUM_FONTS-1 { + color := state.palette[i]; + sdtx_font(cast(s32) i); + sdtx_color3b(color.r, color.g, color.b); + sdtx_printf("Hello '%'!\n", ifx((frame_count & (1<<7)) == 0) then "Welt" else "World"); + sdtx_printf("\tFrame Time:\t\t%.3f\n", frame_time); + sdtx_printf("\tFrame Count:\t%d\t0x%04X\n", frame_count, frame_count); + sdtx_putr("Range Test 1(xyzbla)", 12); + sdtx_putr("\nRange Test 2\n", 32); + sdtx_move_y(2); + } + + sg_begin_pass(*(sg_pass.{ action = state.pass_action, swapchain = xx,force sglue_swapchain() })); + sdtx_draw(); + sg_end_pass(); + sg_commit(); +} + +cleanup :: () #c_call { + sdtx_shutdown(); + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 640, + height = 480, + window_title = "debugtext-printf", + icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} diff --git a/modules/sokol-jai/examples/first.jai b/modules/sokol-jai/examples/first.jai new file mode 100644 index 0000000..85fd6df --- /dev/null +++ b/modules/sokol-jai/examples/first.jai @@ -0,0 +1,33 @@ +#import "Compiler"; +#import "Basic"; +#import "Process"; + +#run { + options := get_build_options(); + assert(options.compile_time_command_line.count >= 1, "Missing args, did you forget to pass the name of the example? (ie: jai first.jai - clear)"); + example_name := options.compile_time_command_line[0]; + use_gl := array_find(options.compile_time_command_line, "-GL"); + + workspace := compiler_create_workspace(tprint("Building example: %", example_name)); + output_path := "../build/"; + + options.output_path = output_path; + options.output_executable_name = example_name; + set_build_options(options, workspace); + compiler_begin_intercept(workspace); + add_build_string(tprint("USE_GL :: %;", use_gl), workspace); + add_build_file(tprint("%/module.jai", example_name), workspace); + + while true { + message := compiler_wait_for_message(); + if message.kind == { + case .ERROR; { exit(1); } + case .COMPLETE; { break; } + } + } + compiler_end_intercept(workspace); + + run_command(tprint("%/%", output_path, example_name)); + + set_build_options_dc(.{ do_output = false }); +} diff --git a/modules/sokol-jai/examples/fontstash-sapp/DroidSansJapanese.ttf b/modules/sokol-jai/examples/fontstash-sapp/DroidSansJapanese.ttf new file mode 100644 index 0000000..ca79221 Binary files /dev/null and b/modules/sokol-jai/examples/fontstash-sapp/DroidSansJapanese.ttf differ diff --git a/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Bold.ttf b/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Bold.ttf new file mode 100644 index 0000000..b6add0e Binary files /dev/null and b/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Bold.ttf differ diff --git a/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Italic.ttf b/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Italic.ttf new file mode 100644 index 0000000..1ef1f75 Binary files /dev/null and b/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Italic.ttf differ diff --git a/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Regular.ttf b/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Regular.ttf new file mode 100644 index 0000000..239ba38 Binary files /dev/null and b/modules/sokol-jai/examples/fontstash-sapp/DroidSerif-Regular.ttf differ diff --git a/modules/sokol-jai/examples/fontstash-sapp/module.jai b/modules/sokol-jai/examples/fontstash-sapp/module.jai new file mode 100644 index 0000000..e1f2ff7 --- /dev/null +++ b/modules/sokol-jai/examples/fontstash-sapp/module.jai @@ -0,0 +1,255 @@ +//------------------------------------------------------------------------------ +// fontstash-sapp/module.jai +// +// Text rendering via fontstash, stb_truetype and sokol_fontstash.h +//------------------------------------------------------------------------------ +#import "Basic"; +#import "File"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#import,dir "../../sokol/gl"(USE_GL=USE_GL); +#import,dir "../../sokol/fontstash"(USE_GL=USE_GL); + +state: struct { + fons: *FONScontext; + dpi_scale: float; + font_normal: s32; + font_italic: s32; + font_bold: s32; + font_japanese: s32; + font_normal_data: [256 * 1024]u8; + font_italic_data: [256 * 1024]u8; + font_bold_data: [256 * 1024]u8; + font_japanese_data: [2 * 1024 * 1024]u8; +} + +// round to next power of 2 (see bit-twiddling-hacks) +round_pow2 :: (v: float) -> s32 { + vi := (cast(u32) v) - 1; + for i : 0..4 { + vi |= (vi >> (1< Matrix4 { + m := Matrix4_Identity; + t := tan(fov * (PI / 360)); + m.coef[0][0] = 1.0 / t; + m.coef[1][1] = aspect / t; + m.coef[2][3] = -1.0; + m.coef[2][2] = (near + far) / (near - far); + m.coef[3][2] = (2.0 * near * far) / (near - far); + m.coef[3][3] = 0; + return m; +} +lookat_mat4 :: (eye: Vector3, center: Vector3, up: Vector3) -> Matrix4 { + m: Matrix4; + f := normalize(center - eye); + s := normalize(cross(f, up)); + u := cross(s, f); + + m.coef[0][0] = s.x; + m.coef[0][1] = u.x; + m.coef[0][2] = -f.x; + + m.coef[1][0] = s.y; + m.coef[1][1] = u.y; + m.coef[1][2] = -f.y; + + m.coef[2][0] = s.z; + m.coef[2][1] = u.z; + m.coef[2][2] = -f.z; + + m.coef[3][0] = -dot(s, eye); + m.coef[3][1] = -dot(u, eye); + m.coef[3][2] = dot(f, eye); + m.coef[3][3] = 1.0; + + return m; +} +multiply_mat4 :: (left: Matrix4, right: Matrix4) -> Matrix4 { + m: Matrix4; + for col : 0 .. 3 { + for row : 0 .. 3 { + m.coef[col][row] = left.coef[0][row] * right.coef[col][0] + + left.coef[1][row] * right.coef[col][1] + + left.coef[2][row] * right.coef[col][2] + + left.coef[3][row] * right.coef[col][3]; + } + } + return m; +} +rotate_mat4 :: (angle: float, axis_unorm: Vector3) -> Matrix4 { + m := Matrix4_Identity; + + axis := normalize(axis_unorm); + sin_theta := sin(radians(angle)); + cos_theta := cos(radians(angle)); + cos_value := 1.0 - cos_theta; + + m.coef[0][0] = (axis.x * axis.x * cos_value) + cos_theta; + m.coef[0][1] = (axis.x * axis.y * cos_value) + (axis.z * sin_theta); + m.coef[0][2] = (axis.x * axis.z * cos_value) - (axis.y * sin_theta); + m.coef[1][0] = (axis.y * axis.x * cos_value) - (axis.z * sin_theta); + m.coef[1][1] = (axis.y * axis.y * cos_value) + cos_theta; + m.coef[1][2] = (axis.y * axis.z * cos_value) + (axis.x * sin_theta); + m.coef[2][0] = (axis.z * axis.x * cos_value) + (axis.y * sin_theta); + m.coef[2][1] = (axis.z * axis.y * cos_value) - (axis.x * sin_theta); + m.coef[2][2] = (axis.z * axis.z * cos_value) + cos_theta; + + return m; +} +translate_mat4 :: (translation: Vector3) -> Matrix4 { + m := Matrix4_Identity; + m.coef[3][0] = translation.x; + m.coef[3][1] = translation.y; + m.coef[3][2] = translation.z; + return m; +} +radians :: (degrees: float) -> float { return degrees * TAU / 360.0; } diff --git a/modules/sokol-jai/examples/offscreen/module.jai b/modules/sokol-jai/examples/offscreen/module.jai new file mode 100644 index 0000000..3c5df2a --- /dev/null +++ b/modules/sokol-jai/examples/offscreen/module.jai @@ -0,0 +1,220 @@ +//------------------------------------------------------------------------------ +// offscreen/main.jai +// +// Render to an offscreen rendertarget texture, and use this texture +// for rendering to the display. +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#import,dir "../../sokol/shape"(USE_GL=USE_GL); +#load "../math.jai"; +#load "./shader.jai"; + +OFFSCREEN_SAMPLE_COUNT :: 1; + +state: struct { + offscreen: struct { + pass_action: sg_pass_action; + attachments: sg_attachments; + pip: sg_pipeline; + bind: sg_bindings; + }; + default: struct { + pass_action: sg_pass_action; + pip: sg_pipeline; + bind: sg_bindings; + }; + donut: sshape_element_range_t; + sphere: sshape_element_range_t; + rx, ry: float; + vertices: [4000]sshape_vertex_t; + indices: [24000]u16; +} + +init :: () #c_call { + push_context,defer_pop; + + sg_setup(*(sg_desc.{ + environment = xx,force sglue_environment(), + logger = .{ func = slog_func }, + })); + + // default pass action: clear to blue-ish + state.default.pass_action = .{ + colors[0] = .{ load_action = .CLEAR, clear_value = .{ 0.25, 0.45, 0.65, 1.0 } }, + }; + + // offscreen pass action: clear to grey + state.offscreen.pass_action = .{ + colors[0] = .{ load_action = .CLEAR, clear_value = .{ 0.25, 0.25, 0.25, 1.0 } }, + }; + + // a render pass with one color- and one depth-attachment image + img_desc := sg_image_desc.{ + render_target = true, + width = 256, + height = 256, + pixel_format = .RGBA8, + sample_count = OFFSCREEN_SAMPLE_COUNT, + }; + color_img := sg_make_image(*img_desc); + img_desc.pixel_format = .DEPTH; + depth_img := sg_make_image(*img_desc); + state.offscreen.attachments = sg_make_attachments(*(sg_attachments_desc.{ + colors[0] = .{ image = color_img }, + depth_stencil = .{ + image = depth_img, + }, + })); + + // a donut shape which is rendered into the offscreen render target, and + // a sphere shape which is rendered into the default framebuffer + buf := sshape_buffer_t.{ + vertices = .{ buffer = .{ ptr = *state.vertices, size = size_of(type_of(state.vertices)) } }, + indices = .{ buffer = .{ ptr = *state.indices, size = size_of(type_of(state.indices)) } }, + }; + + buf = sshape_build_torus(*buf, *(sshape_torus_t.{ + radius = 0.5, + ring_radius = 0.3, + sides = 20, + rings = 30, + })); + state.donut = sshape_element_range(*buf); + + buf = sshape_build_sphere(*buf, *(sshape_sphere_t.{ + radius = 0.5, + slices = 72, + stacks = 40, + })); + state.sphere = sshape_element_range(*buf); + + vbuf := sg_make_buffer(xx,force *sshape_vertex_buffer_desc(*buf)); + ibuf := sg_make_buffer(xx,force *sshape_index_buffer_desc(*buf)); + + // pipeline-state-object for offscreen-rendered donut, don't need texture coord here + offscreen_pip_desc := sg_pipeline_desc.{ + shader = sg_make_shader(*offscreen_shader_desc(sg_query_backend())), + index_type = .UINT16, + cull_mode = .BACK, + sample_count = OFFSCREEN_SAMPLE_COUNT, + depth = .{ + pixel_format = .DEPTH, + compare = .LESS_EQUAL, + write_enabled = true, + }, + colors[0] = .{ pixel_format = .RGBA8 }, + }; + offscreen_pip_desc.layout.buffers[0] = xx,force sshape_vertex_buffer_layout_state(); + offscreen_pip_desc.layout.attrs[ATTR_offscreen_position] = xx,force sshape_position_vertex_attr_state(); + offscreen_pip_desc.layout.attrs[ATTR_offscreen_normal] = xx,force sshape_normal_vertex_attr_state(); + state.offscreen.pip = sg_make_pipeline(*offscreen_pip_desc); + + // and another pipeline-state-object for the default pass + default_pip_desc := sg_pipeline_desc.{ + shader = sg_make_shader(*default_shader_desc(sg_query_backend())), + index_type = .UINT16, + cull_mode = .BACK, + depth = .{ + compare = .LESS_EQUAL, + write_enabled = true, + }, + }; + default_pip_desc.layout.buffers[0] = xx,force sshape_vertex_buffer_layout_state(); + default_pip_desc.layout.attrs[ATTR_default_position] = xx,force sshape_position_vertex_attr_state(); + default_pip_desc.layout.attrs[ATTR_default_normal] = xx,force sshape_normal_vertex_attr_state(); + default_pip_desc.layout.attrs[ATTR_default_texcoord0] = xx,force sshape_texcoord_vertex_attr_state(); + state.default.pip = sg_make_pipeline(*default_pip_desc); + + // a sampler object for sampling the render target as texture + smp := sg_make_sampler(*(sg_sampler_desc.{ + min_filter = .LINEAR, + mag_filter = .LINEAR, + wrap_u = .REPEAT, + wrap_v = .REPEAT, + })); + + // the resource bindings for rendering a non-textured cube into offscreen render target + state.offscreen.bind = .{ + vertex_buffers[0] = vbuf, + index_buffer = ibuf, + }; + + // resource bindings to render a textured shape, using the offscreen render target as texture + state.default.bind = .{ + vertex_buffers[0] = vbuf, + index_buffer = ibuf, + }; + state.default.bind.images[IMG_tex] = color_img; + state.default.bind.samplers[SMP_smp] = smp; +} + +frame :: () #c_call { + push_context,defer_pop; + + t := cast(float) (sapp_frame_duration() * 60.0); + state.rx += 1.0 * t; + state.ry += 2.0 * t; + + // the offscreen pass, rendering an rotating, untextured donut into a render target image + vs_params := Vs_Params.{ + mvp = compute_mvp(rx = state.rx, ry = state.ry, aspect = 1.0, eye_dist = 2.5), + }; + sg_begin_pass(*(sg_pass.{ action = state.offscreen.pass_action, attachments = state.offscreen.attachments })); + sg_apply_pipeline(state.offscreen.pip); + sg_apply_bindings(*state.offscreen.bind); + sg_apply_uniforms(UB_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) })); + sg_draw(state.donut.base_element, state.donut.num_elements, 1); + sg_end_pass(); + + // and the default-pass, rendering a rotating textured sphere which uses the + // previously rendered offscreen render-target as texture + vs_params = Vs_Params.{ + mvp = compute_mvp( + rx = -state.rx * 0.25, + ry = state.ry * 0.25, + aspect = sapp_widthf() / sapp_heightf(), + eye_dist = 2.0, + ), + }; + sg_begin_pass(*(sg_pass.{ action = state.default.pass_action, swapchain = xx,force sglue_swapchain() })); + sg_apply_pipeline(state.default.pip); + sg_apply_bindings(*state.default.bind); + sg_apply_uniforms(UB_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) })); + sg_draw(state.sphere.base_element, state.sphere.num_elements, 1); + sg_end_pass(); + + sg_commit(); +} + +compute_mvp :: (rx: float, ry: float, aspect: float, eye_dist: float) -> Matrix4 { + proj := persp_mat4(fov = 45.0, aspect = aspect, near = 0.01, far = 10.0); + view := lookat_mat4(eye = .{ 0.0, 0.0, eye_dist }, center = .{}, up = .{ 0.0, 1.0, 0.0 }); + view_proj := multiply_mat4(proj, view); + rxm := rotate_mat4(rx, .{ 1.0, 0.0, 0.0 }); + rym := rotate_mat4(ry, .{ 0.0, 1.0, 0.0 }); + model := multiply_mat4(rym, rxm); + mvp := multiply_mat4(view_proj, model); + return mvp; +} + +cleanup :: () #c_call { + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 800, + height = 600, + sample_count = 4, + window_title = "offscreen", + icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} diff --git a/modules/sokol-jai/examples/offscreen/shader.glsl b/modules/sokol-jai/examples/offscreen/shader.glsl new file mode 100644 index 0000000..ec83a4d --- /dev/null +++ b/modules/sokol-jai/examples/offscreen/shader.glsl @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// shaders for offscreen-sapp sample +//------------------------------------------------------------------------------ +@ctype mat4 Matrix4 + +// shared code for all shaders +@block uniforms +layout(binding=0) uniform vs_params { + mat4 mvp; +}; +@end + +// offscreen rendering shaders +@vs vs_offscreen +@include_block uniforms + +in vec4 position; +in vec4 normal; +out vec4 nrm; + +void main() { + gl_Position = mvp * position; + nrm = normal; +} +@end + +@fs fs_offscreen +in vec4 nrm; +out vec4 frag_color; + +void main() { + frag_color = vec4(nrm.xyz * 0.5 + 0.5, 1.0); +} +@end + +@program offscreen vs_offscreen fs_offscreen + +// default-pass shaders +@vs vs_default +@include_block uniforms + +in vec4 position; +in vec4 normal; +in vec2 texcoord0; +out vec4 nrm; +out vec2 uv; + +void main() { + gl_Position = mvp * position; + uv = texcoord0; + nrm = mvp * normal; +} +@end + +@fs fs_default +layout(binding=0) uniform texture2D tex; +layout(binding=0) uniform sampler smp; + +in vec4 nrm; +in vec2 uv; + +out vec4 frag_color; + +void main() { + vec4 c = texture(sampler2D(tex, smp), uv * vec2(20.0, 10.0)); + float l = clamp(dot(nrm.xyz, normalize(vec3(1.0, 1.0, -1.0))), 0.0, 1.0) * 2.0; + frag_color = vec4(c.xyz * (l + 0.25), 1.0); +} +@end + +@program default vs_default fs_default diff --git a/modules/sokol-jai/examples/offscreen/shader.jai b/modules/sokol-jai/examples/offscreen/shader.jai new file mode 100644 index 0000000..4cf6973 --- /dev/null +++ b/modules/sokol-jai/examples/offscreen/shader.jai @@ -0,0 +1,922 @@ +/* + #version:1# (machine generated, don't edit!) + + Generated by sokol-shdc (https://github.com/floooh/sokol-tools) + + Cmdline: + sokol-shdc -i shader.glsl -o shader.jai -l glsl410:metal_macos:hlsl5 -f sokol_jai + + Overview: + ========= + Shader program: 'default': + Get shader desc: default_shader_desc(sg_query_backend()) + Vertex Shader: vs_default + Fragment Shader: fs_default + Attributes: + ATTR_default_position => 0 + ATTR_default_normal => 1 + ATTR_default_texcoord0 => 2 + Shader program: 'offscreen': + Get shader desc: offscreen_shader_desc(sg_query_backend()) + Vertex Shader: vs_offscreen + Fragment Shader: fs_offscreen + Attributes: + ATTR_offscreen_position => 0 + ATTR_offscreen_normal => 1 + Bindings: + Uniform block 'vs_params': + Jai struct: Vs_Params + Bind slot: UB_vs_params => 0 + Image 'tex': + Image type: ._2D + Sample type: .FLOAT + Multisampled: false + Bind slot: IMG_tex => 0 + Sampler 'smp': + Type: .FILTERING + Bind slot: SMP_smp => 0 + Image Sampler Pair 'tex_smp': + Image: tex + Sampler: smp +*/ +ATTR_default_position :: 0; +ATTR_default_normal :: 1; +ATTR_default_texcoord0 :: 2; +ATTR_offscreen_position :: 0; +ATTR_offscreen_normal :: 1; +UB_vs_params :: 0; +IMG_tex :: 0; +SMP_smp :: 0; +Vs_Params :: struct { + mvp: Matrix4; +}; +/* + #version 410 + + uniform vec4 vs_params[4]; + layout(location = 0) in vec4 position; + layout(location = 0) out vec4 nrm; + layout(location = 1) in vec4 normal; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + nrm = normal; + } + +*/ +vs_offscreen_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x6e,0x72, + 0x6d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20, + 0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, + 0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x6e,0x6f,0x72,0x6d, + 0x61,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 410 + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 nrm; + + void main() + { + frag_color = vec4((nrm.xyz * 0.5) + vec3(0.5), 1.0); + } + +*/ +fs_offscreen_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20, + 0x76,0x65,0x63,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61, + 0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x28, + 0x6e,0x72,0x6d,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20,0x30,0x2e,0x35,0x29,0x20,0x2b, + 0x20,0x76,0x65,0x63,0x33,0x28,0x30,0x2e,0x35,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 410 + + uniform vec4 vs_params[4]; + layout(location = 0) in vec4 position; + layout(location = 1) out vec2 uv; + layout(location = 2) in vec2 texcoord0; + layout(location = 0) out vec4 nrm; + layout(location = 1) in vec4 normal; + + void main() + { + mat4 _22 = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]); + gl_Position = _22 * position; + uv = texcoord0; + nrm = _22 * normal; + } + +*/ +vs_default_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x31,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76, + 0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74, + 0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f, + 0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x74,0x34,0x20,0x5f,0x32,0x32,0x20,0x3d,0x20, + 0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30, + 0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c, + 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x5f,0x32,0x32,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x5f,0x32, + 0x32,0x20,0x2a,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + +]; +/* + #version 410 + + uniform sampler2D tex_smp; + + layout(location = 1) in vec2 uv; + layout(location = 0) in vec4 nrm; + layout(location = 0) out vec4 frag_color; + + void main() + { + frag_color = vec4(texture(tex_smp, uv * vec2(20.0, 10.0)).xyz * fma(clamp(dot(nrm.xyz, vec3(0.57735025882720947265625, 0.57735025882720947265625, -0.57735025882720947265625)), 0.0, 1.0), 2.0, 0.25), 1.0); + } + +*/ +fs_default_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69, + 0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75, + 0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20, + 0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30, + 0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69, + 0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x20, + 0x2a,0x20,0x76,0x65,0x63,0x32,0x28,0x32,0x30,0x2e,0x30,0x2c,0x20,0x31,0x30,0x2e, + 0x30,0x29,0x29,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20,0x66,0x6d,0x61,0x28,0x63,0x6c, + 0x61,0x6d,0x70,0x28,0x64,0x6f,0x74,0x28,0x6e,0x72,0x6d,0x2e,0x78,0x79,0x7a,0x2c, + 0x20,0x76,0x65,0x63,0x33,0x28,0x30,0x2e,0x35,0x37,0x37,0x33,0x35,0x30,0x32,0x35, + 0x38,0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37,0x32,0x36,0x35,0x36,0x32,0x35,0x2c, + 0x20,0x30,0x2e,0x35,0x37,0x37,0x33,0x35,0x30,0x32,0x35,0x38,0x38,0x32,0x37,0x32, + 0x30,0x39,0x34,0x37,0x32,0x36,0x35,0x36,0x32,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x35, + 0x37,0x37,0x33,0x35,0x30,0x32,0x35,0x38,0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37, + 0x32,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x2c,0x20,0x32,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x32,0x35,0x29,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + cbuffer vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + }; + + + static float4 gl_Position; + static float4 position; + static float4 nrm; + static float4 normal; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float4 normal : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 nrm : TEXCOORD0; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + nrm = normal; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + normal = stage_input.normal; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.nrm = nrm; + return stage_output; + } +*/ +vs_offscreen_source_hlsl5 := u8.[ + 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x62,0x30,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x72,0x6f,0x77,0x5f,0x6d,0x61,0x6a,0x6f,0x72, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x5f,0x31,0x39,0x5f,0x6d,0x76, + 0x70,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63, + 0x30,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74, + 0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x73, + 0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x6f,0x72, + 0x6d,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49, + 0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x6f,0x72, + 0x6d,0x61,0x6c,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b, + 0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52, + 0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x20, + 0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3a,0x20,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x76,0x65,0x72,0x74, + 0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x75,0x6c,0x28, + 0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x5f,0x31,0x39,0x5f,0x6d,0x76, + 0x70,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x6e,0x6f, + 0x72,0x6d,0x61,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43, + 0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70, + 0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a, + 0x7b,0x0a,0x20,0x20,0x20,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x70,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x6f,0x72,0x6d,0x61, + 0x6c,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e, + 0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x72,0x74, + 0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49, + 0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x67, + 0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74, + 0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x6e,0x72,0x6d,0x20,0x3d, + 0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d, + 0x0a,0x00, +]; +/* + static float4 frag_color; + static float4 nrm; + + struct SPIRV_Cross_Input + { + float4 nrm : TEXCOORD0; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = float4((nrm.xyz * 0.5f) + 0.5f.xxx, 1.0f); + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + nrm = stage_input.nrm; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +fs_offscreen_source_hlsl5 := u8.[ + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73, + 0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f, + 0x52,0x44,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x53,0x56,0x5f, + 0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x6f,0x69, + 0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x28,0x6e,0x72,0x6d,0x2e,0x78,0x79,0x7a, + 0x20,0x2a,0x20,0x30,0x2e,0x35,0x66,0x29,0x20,0x2b,0x20,0x30,0x2e,0x35,0x66,0x2e, + 0x78,0x78,0x78,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69, + 0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x72,0x6d,0x20, + 0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x6e,0x72, + 0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e, + 0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74, + 0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +]; +/* + cbuffer vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + }; + + + static float4 gl_Position; + static float4 position; + static float2 uv; + static float2 texcoord0; + static float4 nrm; + static float4 normal; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float4 normal : TEXCOORD1; + float2 texcoord0 : TEXCOORD2; + }; + + struct SPIRV_Cross_Output + { + float4 nrm : TEXCOORD0; + float2 uv : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + uv = texcoord0; + nrm = mul(normal, _19_mvp); + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + texcoord0 = stage_input.texcoord0; + normal = stage_input.normal; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.uv = uv; + stage_output.nrm = nrm; + return stage_output; + } +*/ +vs_default_source_hlsl5 := u8.[ + 0x63,0x62,0x75,0x66,0x66,0x65,0x72,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x28,0x62,0x30,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x72,0x6f,0x77,0x5f,0x6d,0x61,0x6a,0x6f,0x72, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x5f,0x31,0x39,0x5f,0x6d,0x76, + 0x70,0x20,0x3a,0x20,0x70,0x61,0x63,0x6b,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x63, + 0x30,0x29,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74, + 0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,0x0a,0x73,0x74, + 0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63, + 0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20, + 0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x20,0x3a, + 0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x32,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43, + 0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x20,0x3a,0x20,0x54, + 0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f, + 0x52,0x44,0x31,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x53,0x56, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x6f,0x69,0x64,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a, + 0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x3d,0x20,0x6d,0x75,0x6c,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x2c,0x20,0x5f,0x31,0x39,0x5f,0x6d,0x76,0x70,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x75,0x76,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x6d,0x75,0x6c,0x28,0x6e,0x6f, + 0x72,0x6d,0x61,0x6c,0x2c,0x20,0x5f,0x31,0x39,0x5f,0x6d,0x76,0x70,0x29,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f, + 0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56, + 0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, + 0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3d, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x74,0x65,0x78, + 0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x6f,0x72,0x6d, + 0x61,0x6c,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74, + 0x2e,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x72, + 0x74,0x5f,0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50, + 0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e, + 0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d, + 0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f, + 0x75,0x74,0x70,0x75,0x74,0x2e,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x6e,0x72,0x6d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67, + 0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x00, +]; +/* + Texture2D tex : register(t0); + SamplerState smp : register(s0); + + static float2 uv; + static float4 nrm; + static float4 frag_color; + + struct SPIRV_Cross_Input + { + float4 nrm : TEXCOORD0; + float2 uv : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = float4(tex.Sample(smp, uv * float2(20.0f, 10.0f)).xyz * mad(clamp(dot(nrm.xyz, float3(0.57735025882720947265625f, 0.57735025882720947265625f, -0.57735025882720947265625f)), 0.0f, 1.0f), 2.0f, 0.25f), 1.0f); + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + uv = stage_input.uv; + nrm = stage_input.nrm; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +fs_default_source_hlsl5 := u8.[ + 0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x44,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x3e,0x20,0x74,0x65,0x78,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72, + 0x28,0x74,0x30,0x29,0x3b,0x0a,0x53,0x61,0x6d,0x70,0x6c,0x65,0x72,0x53,0x74,0x61, + 0x74,0x65,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65, + 0x72,0x28,0x73,0x30,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x72,0x6d,0x3b,0x0a,0x73,0x74,0x61, + 0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e,0x72, + 0x6d,0x20,0x3a,0x20,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x3a,0x20,0x54, + 0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73, + 0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20, + 0x3a,0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e, + 0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x74,0x65,0x78, + 0x2e,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x20, + 0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x32,0x30,0x2e,0x30,0x66,0x2c,0x20, + 0x31,0x30,0x2e,0x30,0x66,0x29,0x29,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20,0x6d,0x61, + 0x64,0x28,0x63,0x6c,0x61,0x6d,0x70,0x28,0x64,0x6f,0x74,0x28,0x6e,0x72,0x6d,0x2e, + 0x78,0x79,0x7a,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,0x28,0x30,0x2e,0x35,0x37, + 0x37,0x33,0x35,0x30,0x32,0x35,0x38,0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37,0x32, + 0x36,0x35,0x36,0x32,0x35,0x66,0x2c,0x20,0x30,0x2e,0x35,0x37,0x37,0x33,0x35,0x30, + 0x32,0x35,0x38,0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37,0x32,0x36,0x35,0x36,0x32, + 0x35,0x66,0x2c,0x20,0x2d,0x30,0x2e,0x35,0x37,0x37,0x33,0x35,0x30,0x32,0x35,0x38, + 0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37,0x32,0x36,0x35,0x36,0x32,0x35,0x66,0x29, + 0x29,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x2c,0x20, + 0x32,0x2e,0x30,0x66,0x2c,0x20,0x30,0x2e,0x32,0x35,0x66,0x29,0x2c,0x20,0x31,0x2e, + 0x30,0x66,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28, + 0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75, + 0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f, + 0x69,0x6e,0x70,0x75,0x74,0x2e,0x75,0x76,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6e,0x72, + 0x6d,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e, + 0x6e,0x72,0x6d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52,0x56,0x5f, + 0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, + 0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x7d,0x0a, + 0x00, +]; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + }; + + struct main0_out + { + float4 nrm [[user(locn0)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float4 normal [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.nrm = in.normal; + return out; + } + +*/ +vs_offscreen_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75, + 0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e, + 0x72,0x6d,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67, + 0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72, + 0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75, + 0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72, + 0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61, + 0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20, + 0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f, + 0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30, + 0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30, + 0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x6f,0x75,0x74,0x2e,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x6e,0x6f,0x72, + 0x6d,0x61,0x6c,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, + 0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 nrm [[user(locn0)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.frag_color = float4((in.nrm.xyz * 0.5) + float3(0.5), 1.0); + return out; + } + +*/ +fs_offscreen_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x6e,0x72,0x6d,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30, + 0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20, + 0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x28,0x28,0x69,0x6e,0x2e,0x6e,0x72,0x6d,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20, + 0x30,0x2e,0x35,0x29,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,0x28,0x30,0x2e, + 0x35,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65, + 0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + }; + + struct main0_out + { + float4 nrm [[user(locn0)]]; + float2 uv [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float4 normal [[attribute(1)]]; + float2 texcoord0 [[attribute(2)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.uv = in.texcoord0; + out.nrm = _19.mvp * in.normal; + return out; + } + +*/ +vs_default_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75, + 0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e, + 0x72,0x6d,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75, + 0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d, + 0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75, + 0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x6e, + 0x6f,0x72,0x6d,0x61,0x6c,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74, + 0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x5b,0x5b,0x61, + 0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e, + 0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75, + 0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d, + 0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d, + 0x76,0x70,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x69, + 0x6e,0x2e,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x6f,0x75,0x74,0x2e,0x6e,0x72,0x6d,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d, + 0x76,0x70,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 nrm [[user(locn0)]]; + float2 uv [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(tex.sample(smp, (in.uv * float2(20.0, 10.0))).xyz * fma(fast::clamp(dot(in.nrm.xyz, float3(0.57735025882720947265625, 0.57735025882720947265625, -0.57735025882720947265625)), 0.0, 1.0), 2.0, 0.25), 1.0); + return out; + } + +*/ +fs_default_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x6e,0x72,0x6d,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29, + 0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74, + 0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30, + 0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78,0x74,0x75, + 0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65,0x78,0x20, + 0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d,0x2c,0x20, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b,0x73,0x61, + 0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20, + 0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x34,0x28,0x74,0x65,0x78,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d,0x70, + 0x2c,0x20,0x28,0x69,0x6e,0x2e,0x75,0x76,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x32,0x28,0x32,0x30,0x2e,0x30,0x2c,0x20,0x31,0x30,0x2e,0x30,0x29,0x29,0x29,0x2e, + 0x78,0x79,0x7a,0x20,0x2a,0x20,0x66,0x6d,0x61,0x28,0x66,0x61,0x73,0x74,0x3a,0x3a, + 0x63,0x6c,0x61,0x6d,0x70,0x28,0x64,0x6f,0x74,0x28,0x69,0x6e,0x2e,0x6e,0x72,0x6d, + 0x2e,0x78,0x79,0x7a,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,0x28,0x30,0x2e,0x35, + 0x37,0x37,0x33,0x35,0x30,0x32,0x35,0x38,0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37, + 0x32,0x36,0x35,0x36,0x32,0x35,0x2c,0x20,0x30,0x2e,0x35,0x37,0x37,0x33,0x35,0x30, + 0x32,0x35,0x38,0x38,0x32,0x37,0x32,0x30,0x39,0x34,0x37,0x32,0x36,0x35,0x36,0x32, + 0x35,0x2c,0x20,0x2d,0x30,0x2e,0x35,0x37,0x37,0x33,0x35,0x30,0x32,0x35,0x38,0x38, + 0x32,0x37,0x32,0x30,0x39,0x34,0x37,0x32,0x36,0x35,0x36,0x32,0x35,0x29,0x29,0x2c, + 0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x2c,0x20,0x32,0x2e,0x30,0x2c, + 0x20,0x30,0x2e,0x32,0x35,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x00, +]; +default_shader_desc :: (backend: sg_backend) -> sg_shader_desc { + desc: sg_shader_desc; + desc.label = "default_shader"; + if backend == { + case .GLCORE; + desc.vertex_func.source = xx *vs_default_source_glsl410; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_default_source_glsl410; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "position"; + desc.attrs[1].glsl_name = "normal"; + desc.attrs[2].glsl_name = "texcoord0"; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4; + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 4; + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "vs_params"; + desc.images[0].stage = .FRAGMENT; + desc.images[0].multisampled = false; + desc.images[0].image_type = ._2D; + desc.images[0].sample_type = .FLOAT; + desc.samplers[0].stage = .FRAGMENT; + desc.samplers[0].sampler_type = .FILTERING; + desc.image_sampler_pairs[0].stage = .FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + desc.image_sampler_pairs[0].glsl_name = "tex_smp"; + case .D3D11; + desc.vertex_func.source = xx *vs_default_source_hlsl5; + desc.vertex_func.d3d11_target = "vs_5_0"; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_default_source_hlsl5; + desc.fragment_func.d3d11_target = "ps_5_0"; + desc.fragment_func.entry = "main"; + desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + desc.attrs[0].hlsl_sem_index = 0; + desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + desc.attrs[1].hlsl_sem_index = 1; + desc.attrs[2].hlsl_sem_name = "TEXCOORD"; + desc.attrs[2].hlsl_sem_index = 2; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].hlsl_register_b_n = 0; + desc.images[0].stage = .FRAGMENT; + desc.images[0].multisampled = false; + desc.images[0].image_type = ._2D; + desc.images[0].sample_type = .FLOAT; + desc.images[0].hlsl_register_t_n = 0; + desc.samplers[0].stage = .FRAGMENT; + desc.samplers[0].sampler_type = .FILTERING; + desc.samplers[0].hlsl_register_s_n = 0; + desc.image_sampler_pairs[0].stage = .FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + case .METAL_MACOS; + desc.vertex_func.source = xx *vs_default_source_metal_macos; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = xx *fs_default_source_metal_macos; + desc.fragment_func.entry = "main0"; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].msl_buffer_n = 0; + desc.images[0].stage = .FRAGMENT; + desc.images[0].multisampled = false; + desc.images[0].image_type = ._2D; + desc.images[0].sample_type = .FLOAT; + desc.images[0].msl_texture_n = 0; + desc.samplers[0].stage = .FRAGMENT; + desc.samplers[0].sampler_type = .FILTERING; + desc.samplers[0].msl_sampler_n = 0; + desc.image_sampler_pairs[0].stage = .FRAGMENT; + desc.image_sampler_pairs[0].image_slot = 0; + desc.image_sampler_pairs[0].sampler_slot = 0; + } + return desc; +} +offscreen_shader_desc :: (backend: sg_backend) -> sg_shader_desc { + desc: sg_shader_desc; + desc.label = "offscreen_shader"; + if backend == { + case .GLCORE; + desc.vertex_func.source = xx *vs_offscreen_source_glsl410; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_offscreen_source_glsl410; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "position"; + desc.attrs[1].glsl_name = "normal"; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4; + desc.uniform_blocks[0].glsl_uniforms[0].array_count = 4; + desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "vs_params"; + case .D3D11; + desc.vertex_func.source = xx *vs_offscreen_source_hlsl5; + desc.vertex_func.d3d11_target = "vs_5_0"; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_offscreen_source_hlsl5; + desc.fragment_func.d3d11_target = "ps_5_0"; + desc.fragment_func.entry = "main"; + desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + desc.attrs[0].hlsl_sem_index = 0; + desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + desc.attrs[1].hlsl_sem_index = 1; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].hlsl_register_b_n = 0; + case .METAL_MACOS; + desc.vertex_func.source = xx *vs_offscreen_source_metal_macos; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = xx *fs_offscreen_source_metal_macos; + desc.fragment_func.entry = "main0"; + desc.uniform_blocks[0].stage = .VERTEX; + desc.uniform_blocks[0].layout = .STD140; + desc.uniform_blocks[0].size = 64; + desc.uniform_blocks[0].msl_buffer_n = 0; + } + return desc; +} diff --git a/modules/sokol-jai/examples/saudio/module.jai b/modules/sokol-jai/examples/saudio/module.jai new file mode 100644 index 0000000..436c912 --- /dev/null +++ b/modules/sokol-jai/examples/saudio/module.jai @@ -0,0 +1,69 @@ +//------------------------------------------------------------------------------ +// saudio/module.jai +// Test sokol-audio. +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#import,dir "../../sokol/debugtext"(USE_GL=USE_GL); +#import,dir "../../sokol/audio"; + +NUM_SAMPLES :: 32; + +state: struct { + pass_action: sg_pass_action; + even_odd: u32; + sample_pos: int; + samples: [NUM_SAMPLES]float; +} = .{ + pass_action = .{ + colors = .[ .{ load_action = .CLEAR, clear_value = .{ 1.0, 0.5, 0.0, 1.0 }, }, .{}, .{}, .{}, ], + }, +}; + +init :: () #c_call { + sg_setup(*(sg_desc.{ + environment = xx,force sglue_environment(), + logger = .{ func = slog_func }, + })); + saudio_setup(*(saudio_desc.{ + logger = .{ func = slog_func }, + })); +} + +frame :: () #c_call { + num_frames := saudio_expect(); + for i : 0 .. num_frames - 1 { + state.even_odd += 1; + state.samples[state.sample_pos] = ifx((state.even_odd & (1<<5)) == 0) then 0.05 else -0.05; + state.sample_pos += 1; + if state.sample_pos == NUM_SAMPLES { + state.sample_pos = 0; + saudio_push(*state.samples[0], NUM_SAMPLES); + } + } + + sg_begin_pass(*(sg_pass.{ action = state.pass_action, swapchain = xx,force sglue_swapchain() })); + sg_end_pass(); + sg_commit(); +} + +cleanup :: () #c_call { + saudio_shutdown(); + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 400, + height = 300, + window_title = "saudio", + icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} diff --git a/modules/sokol-jai/examples/sgl-context-sapp/module.jai b/modules/sokol-jai/examples/sgl-context-sapp/module.jai new file mode 100644 index 0000000..504c598 --- /dev/null +++ b/modules/sokol-jai/examples/sgl-context-sapp/module.jai @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// sgl-context-sapp/module.jai +// +// Demonstrates how to render in different render passes with sokol_gl.h +// using sokol-gl contexts. +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#import,dir "../../sokol/shape"(USE_GL=USE_GL); +#import,dir "../../sokol/gl"(USE_GL=USE_GL); +#load "../math.jai"; + +SGL_DEFAULT_CONTEXT :: sgl_context.{ 0x00010001 }; + +OFFSCREEN_PIXELFORMAT :: sg_pixel_format.RGBA8; +OFFSCREEN_SAMPLECOUNT :: 1; +OFFSCREEN_WIDTH :: 32; +OFFSCREEN_HEIGHT :: 32; + +state: struct { + angle_deg: float64; + offscreen: struct { + pass_action: sg_pass_action; + attachments: sg_attachments; + img: sg_image; + sgl_ctx: sgl_context; + }; + display: struct { + pass_action: sg_pass_action; + smp: sg_sampler; + sgl_pip: sgl_pipeline; + }; +} + +init :: () #c_call { + push_context,defer_pop; + + sg_setup(*(sg_desc.{ + environment = xx,force sglue_environment(), + logger.func = slog_func, + })); + + // setup sokol-gl with the default context compatible with the default render pass + sgl_setup(*(sgl_desc_t.{ + max_vertices = 64, + max_commands = 16, + logger.func = slog_func, + })); + + // pass action and pipeline for the default render pass + state.display.pass_action = .{ + colors[0] = .{ + load_action = .CLEAR, + clear_value = .{ 0.5, 0.7, 1.0, 1.0 }, + }, + }; + state.display.sgl_pip = sgl_context_make_pipeline(sgl_default_context(), xx *(sg_pipeline_desc.{ + cull_mode = .BACK, + depth = .{ + write_enabled = true, + compare = .LESS_EQUAL, + }, + })); + + // create a sokol-gl context compatible with the offscreen render pass + // (specific color pixel format, no depth-stencil-surface, no MSAA) + state.offscreen.sgl_ctx = sgl_make_context(*(sgl_context_desc_t.{ + max_vertices = 8, + max_commands = 4, + color_format = xx OFFSCREEN_PIXELFORMAT, + depth_format = .NONE, + sample_count = OFFSCREEN_SAMPLECOUNT, + })); + + // create an offscreen render target texture, pass, and pass_action + state.offscreen.img = sg_make_image(*(sg_image_desc.{ + render_target = true, + width = OFFSCREEN_WIDTH, + height = OFFSCREEN_HEIGHT, + pixel_format = xx OFFSCREEN_PIXELFORMAT, + sample_count = xx OFFSCREEN_SAMPLECOUNT, + })); + state.offscreen.attachments = sg_make_attachments(*(sg_attachments_desc.{ + colors[0].image = state.offscreen.img, + })); + state.offscreen.pass_action = .{ + colors[0] = .{ + load_action = .CLEAR, + clear_value = .{ 0.0, 0.0, 0.0, 1.0 }, + }, + }; + + // a sampler for sampling the offscreen render target + state.display.smp = sg_make_sampler(*(sg_sampler_desc.{ + wrap_u = .CLAMP_TO_EDGE, + wrap_v = .CLAMP_TO_EDGE, + min_filter = .NEAREST, + mag_filter = .NEAREST, + })); +} + +frame :: () #c_call { + push_context,defer_pop; + + state.angle_deg += sapp_frame_duration() * 60.0; + a := sgl_rad(cast(float) state.angle_deg); + + // draw a rotating quad into the offscreen render target texture + sgl_set_context(state.offscreen.sgl_ctx); + sgl_defaults(); + sgl_matrix_mode_modelview(); + sgl_rotate(a, 0.0, 0.0, 1.0); + draw_quad(); + + // draw a rotating 3D cube, using the offscreen render target as texture + sgl_set_context(SGL_DEFAULT_CONTEXT); + sgl_defaults(); + sgl_enable_texture(); + sgl_texture(xx,force state.offscreen.img, xx,force state.display.smp); + sgl_load_pipeline(state.display.sgl_pip); + sgl_matrix_mode_projection(); + sgl_perspective(sgl_rad(45.0), sapp_widthf()/sapp_heightf(), 0.1, 100.0); + eye := float.[ sin(a) * 6.0, sin(a) * 3.0, cos(a) * 6.0 ]; + sgl_matrix_mode_modelview(); + sgl_lookat(eye[0], eye[1], eye[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); + draw_cube(); + + // do the actual offscreen and display rendering in sokol-gfx passes + sg_begin_pass(*(sg_pass.{ action = state.offscreen.pass_action, attachments = state.offscreen.attachments })); + sgl_context_draw(state.offscreen.sgl_ctx); + sg_end_pass(); + sg_begin_pass(*(sg_pass.{ action = state.display.pass_action, swapchain = xx,force sglue_swapchain() })); + sgl_context_draw(SGL_DEFAULT_CONTEXT); + sg_end_pass(); + sg_commit(); +} + +// helper function to draw a colored quad with sokol-gl +draw_quad :: () { + sgl_begin_quads(); + sgl_v2f_c3b( 0.0, -1.0, 255, 0, 0); + sgl_v2f_c3b( 1.0, 0.0, 0, 0, 255); + sgl_v2f_c3b( 0.0, 1.0, 0, 255, 255); + sgl_v2f_c3b(-1.0, 0.0, 0, 255, 0); + sgl_end(); +} + +// helper function to draw a textured cube with sokol-gl +draw_cube :: () { + sgl_begin_quads(); + sgl_v3f_t2f(-1.0, 1.0, -1.0, 0.0, 1.0); + sgl_v3f_t2f( 1.0, 1.0, -1.0, 1.0, 1.0); + sgl_v3f_t2f( 1.0, -1.0, -1.0, 1.0, 0.0); + sgl_v3f_t2f(-1.0, -1.0, -1.0, 0.0, 0.0); + sgl_v3f_t2f(-1.0, -1.0, 1.0, 0.0, 1.0); + sgl_v3f_t2f( 1.0, -1.0, 1.0, 1.0, 1.0); + sgl_v3f_t2f( 1.0, 1.0, 1.0, 1.0, 0.0); + sgl_v3f_t2f(-1.0, 1.0, 1.0, 0.0, 0.0); + sgl_v3f_t2f(-1.0, -1.0, 1.0, 0.0, 1.0); + sgl_v3f_t2f(-1.0, 1.0, 1.0, 1.0, 1.0); + sgl_v3f_t2f(-1.0, 1.0, -1.0, 1.0, 0.0); + sgl_v3f_t2f(-1.0, -1.0, -1.0, 0.0, 0.0); + sgl_v3f_t2f( 1.0, -1.0, 1.0, 0.0, 1.0); + sgl_v3f_t2f( 1.0, -1.0, -1.0, 1.0, 1.0); + sgl_v3f_t2f( 1.0, 1.0, -1.0, 1.0, 0.0); + sgl_v3f_t2f( 1.0, 1.0, 1.0, 0.0, 0.0); + sgl_v3f_t2f( 1.0, -1.0, -1.0, 0.0, 1.0); + sgl_v3f_t2f( 1.0, -1.0, 1.0, 1.0, 1.0); + sgl_v3f_t2f(-1.0, -1.0, 1.0, 1.0, 0.0); + sgl_v3f_t2f(-1.0, -1.0, -1.0, 0.0, 0.0); + sgl_v3f_t2f(-1.0, 1.0, -1.0, 0.0, 1.0); + sgl_v3f_t2f(-1.0, 1.0, 1.0, 1.0, 1.0); + sgl_v3f_t2f( 1.0, 1.0, 1.0, 1.0, 0.0); + sgl_v3f_t2f( 1.0, 1.0, -1.0, 0.0, 0.0); + sgl_end(); +} + +cleanup :: () #c_call { + sgl_shutdown(); + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 800, + height = 600, + sample_count = 4, + window_title = "sokol-gl contexts (sapp)", + icon.sokol_default = true, + logger.func = slog_func, + })); +} diff --git a/modules/sokol-jai/examples/triangle/module.jai b/modules/sokol-jai/examples/triangle/module.jai new file mode 100644 index 0000000..ec2009f --- /dev/null +++ b/modules/sokol-jai/examples/triangle/module.jai @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// triangle/module.jai +// +// Hello Triangle sample. +//------------------------------------------------------------------------------ +#import "Basic"; +#import,dir "../../sokol/log"(USE_GL=USE_GL); +#import,dir "../../sokol/gfx"(USE_GL=USE_GL); +#import,dir "../../sokol/app"(USE_GL=USE_GL); +#import,dir "../../sokol/glue"(USE_GL=USE_GL); +#load "./shader.jai"; + +state: struct { + pip: sg_pipeline; + bind: sg_bindings; + pass_action: sg_pass_action; +} + +init :: () #c_call { + push_context,defer_pop; + + sg_setup(*(sg_desc.{ + environment = xx,force sglue_environment(), + logger = .{ func = slog_func }, + })); + + // a vertex buffer with 3 vertices + vertices := float.[ + // positions // colors + 0.0, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, + 0.5, -0.5, 0.5, 0.0, 1.0, 0.0, 1.0, + -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, + ]; + buffer := sg_buffer_desc.{ data = .{ ptr = *vertices, size = size_of(type_of(vertices)) } }; + state.bind.vertex_buffers[0] = sg_make_buffer(*buffer); + + // create shader from code-generated sg_shader_desc + shd := sg_make_shader(*triangle_shader_desc(sg_query_backend())); + + // create a shader and pipeline object (default render states are fine for triangle) + pipeline: sg_pipeline_desc; + pipeline.shader = shd; + pipeline.layout.attrs[ATTR_triangle_position] = .{ format = .FLOAT3 }; + pipeline.layout.attrs[ATTR_triangle_color0] = .{ format = .FLOAT4 }; + state.pip = sg_make_pipeline(*pipeline); + + // a pass action to clear framebuffer to black + state.pass_action.colors[0] = .{ load_action = .CLEAR, clear_value = .{ 0, 0, 0,1 } }; +} + +frame :: () #c_call { + sg_begin_pass(*(sg_pass.{ action = state.pass_action, swapchain = xx,force sglue_swapchain() })); + sg_apply_pipeline(state.pip); + sg_apply_bindings(*state.bind); + sg_draw(0, 3, 1); + sg_end_pass(); + sg_commit(); +} + +cleanup :: () #c_call { + sg_shutdown(); +} + +main :: () { + sapp_run(*(sapp_desc.{ + init_cb = init, + frame_cb = frame, + cleanup_cb = cleanup, + width = 640, + height = 480, + window_title = "triangle", + icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} diff --git a/modules/sokol-jai/examples/triangle/shader.glsl b/modules/sokol-jai/examples/triangle/shader.glsl new file mode 100644 index 0000000..0c31a1e --- /dev/null +++ b/modules/sokol-jai/examples/triangle/shader.glsl @@ -0,0 +1,22 @@ +@vs vs +in vec4 position; +in vec4 color0; + +out vec4 color; + +void main() { + gl_Position = position; + color = color0; +} +@end + +@fs fs +in vec4 color; +out vec4 frag_color; + +void main() { + frag_color = color; +} +@end + +@program triangle vs fs diff --git a/modules/sokol-jai/examples/triangle/shader.jai b/modules/sokol-jai/examples/triangle/shader.jai new file mode 100644 index 0000000..6c48370 --- /dev/null +++ b/modules/sokol-jai/examples/triangle/shader.jai @@ -0,0 +1,341 @@ +/* + #version:1# (machine generated, don't edit!) + + Generated by sokol-shdc (https://github.com/floooh/sokol-tools) + + Cmdline: + sokol-shdc -i shader.glsl -o shader.jai -l glsl410:metal_macos:hlsl5 -f sokol_jai + + Overview: + ========= + Shader program: 'triangle': + Get shader desc: triangle_shader_desc(sg_query_backend()) + Vertex Shader: vs + Fragment Shader: fs + Attributes: + ATTR_triangle_position => 0 + ATTR_triangle_color0 => 1 + Bindings: +*/ +ATTR_triangle_position :: 0; +ATTR_triangle_color0 :: 1; +/* + #version 410 + + layout(location = 0) in vec4 position; + layout(location = 0) out vec4 color; + layout(location = 1) in vec4 color0; + + void main() + { + gl_Position = position; + color = color0; + } + +*/ +vs_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69, + 0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a, + 0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x00, +]; +/* + #version 410 + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 color; + + void main() + { + frag_color = color; + } + +*/ +fs_source_glsl410 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20, + 0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69, + 0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + static float4 gl_Position; + static float4 position; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float4 color0 : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 color : TEXCOORD0; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = position; + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.color = color; + return stage_output; + } +*/ +vs_source_hlsl5 := u8.[ + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69, + 0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73, + 0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20, + 0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x54, + 0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x31,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73, + 0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45,0x58, + 0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a, + 0x20,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x76,0x65,0x72,0x74,0x5f,0x6d,0x61,0x69,0x6e, + 0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72, + 0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28, + 0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x49,0x6e,0x70,0x75, + 0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e,0x63, + 0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x72,0x74,0x5f, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52, + 0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x73,0x74,0x61, + 0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20, + 0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74, + 0x3b,0x0a,0x7d,0x0a,0x00, +]; +/* + static float4 frag_color; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 color : TEXCOORD0; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +fs_source_hlsl5 := u8.[ + 0x73,0x74,0x61,0x74,0x69,0x63,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x73,0x74,0x61,0x74,0x69,0x63, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a, + 0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x54,0x45, + 0x58,0x43,0x4f,0x4f,0x52,0x44,0x30,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72, + 0x75,0x63,0x74,0x20,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f, + 0x4f,0x75,0x74,0x70,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a, + 0x20,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x76,0x6f,0x69,0x64,0x20,0x66,0x72,0x61,0x67,0x5f,0x6d,0x61,0x69,0x6e,0x28, + 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x53, + 0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75, + 0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x53,0x50,0x49,0x52,0x56,0x5f,0x43,0x72,0x6f, + 0x73,0x73,0x5f,0x49,0x6e,0x70,0x75,0x74,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69, + 0x6e,0x70,0x75,0x74,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x70,0x75,0x74,0x2e, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x53,0x50,0x49,0x52, + 0x56,0x5f,0x43,0x72,0x6f,0x73,0x73,0x5f,0x4f,0x75,0x74,0x70,0x75,0x74,0x20,0x73, + 0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x2e,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72, + 0x6e,0x20,0x73,0x74,0x61,0x67,0x65,0x5f,0x6f,0x75,0x74,0x70,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 color [[user(locn0)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float4 color0 [[attribute(1)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = in.position; + out.color = in.color0; + return out; + } + +*/ +vs_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73, + 0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20, + 0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65, + 0x5f,0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d, + 0x0a,0x0a,0x00, +]; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 color [[user(locn0)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.frag_color = in.color; + return out; + } + +*/ +fs_source_metal_macos := u8.[ + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63, + 0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d, + 0x65,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61, + 0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20, + 0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72, + 0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +triangle_shader_desc :: (backend: sg_backend) -> sg_shader_desc { + desc: sg_shader_desc; + desc.label = "triangle_shader"; + if backend == { + case .GLCORE; + desc.vertex_func.source = xx *vs_source_glsl410; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_source_glsl410; + desc.fragment_func.entry = "main"; + desc.attrs[0].glsl_name = "position"; + desc.attrs[1].glsl_name = "color0"; + case .D3D11; + desc.vertex_func.source = xx *vs_source_hlsl5; + desc.vertex_func.d3d11_target = "vs_5_0"; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_source_hlsl5; + desc.fragment_func.d3d11_target = "ps_5_0"; + desc.fragment_func.entry = "main"; + desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + desc.attrs[0].hlsl_sem_index = 0; + desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + desc.attrs[1].hlsl_sem_index = 1; + case .METAL_MACOS; + desc.vertex_func.source = xx *vs_source_metal_macos; + desc.vertex_func.entry = "main0"; + desc.fragment_func.source = xx *fs_source_metal_macos; + desc.fragment_func.entry = "main0"; + } + return desc; +} diff --git a/modules/sokol-jai/libclang_rt.osx.a b/modules/sokol-jai/libclang_rt.osx.a new file mode 100644 index 0000000..3eb272c Binary files /dev/null and b/modules/sokol-jai/libclang_rt.osx.a differ diff --git a/modules/sokol-jai/sokol/app/module.jai b/modules/sokol-jai/sokol/app/module.jai new file mode 100644 index 0000000..046017c --- /dev/null +++ b/modules/sokol-jai/sokol/app/module.jai @@ -0,0 +1,1947 @@ +// machine generated, do not edit + +/* + + sokol_app.h -- cross-platform application wrapper + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_APP_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the 3D-API + which should be initialized by sokol_app.h (this must also match + the backend selected for sokol_gfx.h if both are used in the same + project): + + #define SOKOL_GLCORE + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_NOAPI + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_WIN32_FORCE_MAIN - define this on Win32 to add a main() entry point + SOKOL_WIN32_FORCE_WINMAIN - define this on Win32 to add a WinMain() entry point (enabled by default unless SOKOL_WIN32_FORCE_MAIN or SOKOL_NO_ENTRY is defined) + SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function + SOKOL_APP_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_APP_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + If sokol_app.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + if SOKOL_WIN32_FORCE_MAIN and SOKOL_WIN32_FORCE_WINMAIN are both defined, + it is up to the developer to define the desired subsystem. + + On Linux, SOKOL_GLCORE can use either GLX or EGL. + GLX is default, set SOKOL_FORCE_EGL to override. + + For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp + + Portions of the Windows and Linux GL initialization, event-, icon- etc... code + have been taken from GLFW (http://www.glfw.org/). + + iOS onscreen keyboard support 'inspired' by libgdx. + + Link with the following system libraries: + + - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit + - on macOS with GL: Cocoa, QuartzCore, OpenGL + - on iOS with Metal: Foundation, UIKit, Metal, MetalKit + - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit + - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) + - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) + - on Android: GLESv3, EGL, log, android + - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib + - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined + - link with the following libs: -lkernel32 -luser32 -lshell32 + - additionally with the GL backend: -lgdi32 + - additionally with the D3D11 backend: -ld3d11 -ldxgi + + On Linux, you also need to use the -pthread compiler and linker option, otherwise weird + things will happen, see here for details: https://github.com/floooh/sokol/issues/376 + + On macOS and iOS, the implementation must be compiled as Objective-C. + + FEATURE OVERVIEW + ================ + sokol_app.h provides a minimalistic cross-platform API which + implements the 'application-wrapper' parts of a 3D application: + + - a common application entry function + - creates a window and 3D-API context/device with a 'default framebuffer' + - makes the rendered frame visible + - provides keyboard-, mouse- and low-level touch-events + - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android + - 3D-APIs: Metal, D3D11, GL4.1, GL4.3, GLES3, WebGL, WebGL2, NOAPI + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 4.x | YES | YES | YES | --- | --- | --- + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | YES | --- | --- | --- | --- | --- + noapi | YES | TODO | TODO | --- | TODO | --- + KEY_DOWN | YES | YES | YES | SOME | TODO | YES + KEY_UP | YES | YES | YES | SOME | TODO | YES + CHAR | YES | YES | YES | YES | TODO | YES + MOUSE_DOWN | YES | YES | YES | --- | --- | YES + MOUSE_UP | YES | YES | YES | --- | --- | YES + MOUSE_SCROLL | YES | YES | YES | --- | --- | YES + MOUSE_MOVE | YES | YES | YES | --- | --- | YES + MOUSE_ENTER | YES | YES | YES | --- | --- | YES + MOUSE_LEAVE | YES | YES | YES | --- | --- | YES + TOUCHES_BEGAN | --- | --- | --- | YES | YES | YES + TOUCHES_MOVED | --- | --- | --- | YES | YES | YES + TOUCHES_ENDED | --- | --- | --- | YES | YES | YES + TOUCHES_CANCELLED | --- | --- | --- | YES | YES | YES + RESIZED | YES | YES | YES | YES | YES | YES + ICONIFIED | YES | YES | YES | --- | --- | --- + RESTORED | YES | YES | YES | --- | --- | --- + FOCUSED | YES | YES | YES | --- | --- | YES + UNFOCUSED | YES | YES | YES | --- | --- | YES + SUSPENDED | --- | --- | --- | YES | YES | TODO + RESUMED | --- | --- | --- | YES | YES | TODO + QUIT_REQUESTED | YES | YES | YES | --- | --- | YES + IME | TODO | TODO? | TODO | ??? | TODO | ??? + key repeat flag | YES | YES | YES | --- | --- | YES + windowed | YES | YES | YES | --- | --- | YES + fullscreen | YES | YES | YES | YES | YES | --- + mouse hide | YES | YES | YES | --- | --- | YES + mouse lock | YES | YES | YES | --- | --- | YES + set cursor type | YES | YES | YES | --- | --- | YES + screen keyboard | --- | --- | --- | YES | TODO | YES + swap interval | YES | YES | YES | YES | TODO | YES + high-dpi | YES | YES | TODO | YES | YES | YES + clipboard | YES | YES | YES | --- | --- | YES + MSAA | YES | YES | YES | YES | YES | YES + drag'n'drop | YES | YES | YES | --- | --- | YES + window icon | YES | YES(1)| YES | --- | --- | YES + + (1) macOS has no regular window icons, instead the dock icon is changed + (2) supported with EGL only (not GLX) + + STEP BY STEP + ============ + --- Add a sokol_main() function to your code which returns a sapp_desc structure + with initialization parameters and callback function pointers. This + function is called very early, usually at the start of the + platform's entry function (e.g. main or WinMain). You should do as + little as possible here, since the rest of your code might be called + from another thread (this depends on the platform): + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + .width = 640, + .height = 480, + .init_cb = my_init_func, + .frame_cb = my_frame_func, + .cleanup_cb = my_cleanup_func, + .event_cb = my_event_func, + ... + }; + } + + To get any logging output in case of errors you need to provide a log + callback. The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + There are many more setup parameters, but these are the most important. + For a complete list search for the sapp_desc structure declaration + below. + + DO NOT call any sokol-app function from inside sokol_main(), since + sokol-app will not be initialized at this point. + + The .width and .height parameters are the preferred size of the 3D + rendering canvas. The actual size may differ from this depending on + platform and other circumstances. Also the canvas size may change at + any time (for instance when the user resizes the application window, + or rotates the mobile device). You can just keep .width and .height + zero-initialized to open a default-sized window (what "default-size" + exactly means is platform-specific, but usually it's a size that covers + most of, but not all, of the display). + + All provided function callbacks will be called from the same thread, + but this may be different from the thread where sokol_main() was called. + + .init_cb (void (*)(void)) + This function is called once after the application window, + 3D rendering context and swap chain have been created. The + function takes no arguments and has no return value. + .frame_cb (void (*)(void)) + This is the per-frame callback, which is usually called 60 + times per second. This is where your application would update + most of its state and perform all rendering. + .cleanup_cb (void (*)(void)) + The cleanup callback is called once right before the application + quits. + .event_cb (void (*)(const sapp_event* event)) + The event callback is mainly for input handling, but is also + used to communicate other types of events to the application. Keep the + event_cb struct member zero-initialized if your application doesn't require + event handling. + + As you can see, those 'standard callbacks' don't have a user_data + argument, so any data that needs to be preserved between callbacks + must live in global variables. If keeping state in global variables + is not an option, there's an alternative set of callbacks with + an additional user_data pointer argument: + + .user_data (void*) + The user-data argument for the callbacks below + .init_userdata_cb (void (*)(void* user_data)) + .frame_userdata_cb (void (*)(void* user_data)) + .cleanup_userdata_cb (void (*)(void* user_data)) + .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) + + The function sapp_userdata() can be used to query the user_data + pointer provided in the sapp_desc struct. + + You can also call sapp_query_desc() to get a copy of the + original sapp_desc structure. + + NOTE that there's also an alternative compile mode where sokol_app.h + doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. + + --- Implement the initialization callback function (init_cb), this is called + once after the rendering surface, 3D API and swap chain have been + initialized by sokol_app. All sokol-app functions can be called + from inside the initialization callback, the most useful functions + at this point are: + + int sapp_width(void) + int sapp_height(void) + Returns the current width and height of the default framebuffer in pixels, + this may change from one frame to the next, and it may be different + from the initial size provided in the sapp_desc struct. + + float sapp_widthf(void) + float sapp_heightf(void) + These are alternatives to sapp_width() and sapp_height() which return + the default framebuffer size as float values instead of integer. This + may help to prevent casting back and forth between int and float + in more strongly typed languages than C and C++. + + double sapp_frame_duration(void) + Returns the frame duration in seconds averaged over a number of + frames to smooth out any jittering spikes. + + int sapp_color_format(void) + int sapp_depth_format(void) + The color and depth-stencil pixelformats of the default framebuffer, + as integer values which are compatible with sokol-gfx's + sg_pixel_format enum (so that they can be plugged directly in places + where sg_pixel_format is expected). Possible values are: + + 23 == SG_PIXELFORMAT_RGBA8 + 28 == SG_PIXELFORMAT_BGRA8 + 42 == SG_PIXELFORMAT_DEPTH + 43 == SG_PIXELFORMAT_DEPTH_STENCIL + + int sapp_sample_count(void) + Return the MSAA sample count of the default framebuffer. + + const void* sapp_metal_get_device(void) + const void* sapp_metal_get_current_drawable(void) + const void* sapp_metal_get_depth_stencil_texture(void) + const void* sapp_metal_get_msaa_color_texture(void) + If the Metal backend has been selected, these functions return pointers + to various Metal API objects required for rendering, otherwise + they return a null pointer. These void pointers are actually + Objective-C ids converted with a (ARC) __bridge cast so that + the ids can be tunneled through C code. Also note that the returned + pointers may change from one frame to the next, only the Metal device + object is guaranteed to stay the same. + + const void* sapp_macos_get_window(void) + On macOS, get the NSWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_ios_get_window(void) + On iOS, get the UIWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_d3d11_get_device(void) + const void* sapp_d3d11_get_device_context(void) + const void* sapp_d3d11_get_render_view(void) + const void* sapp_d3d11_get_resolve_view(void); + const void* sapp_d3d11_get_depth_stencil_view(void) + Similar to the sapp_metal_* functions, the sapp_d3d11_* functions + return pointers to D3D11 API objects required for rendering, + only if the D3D11 backend has been selected. Otherwise they + return a null pointer. Note that the returned pointers to the + render-target-view and depth-stencil-view may change from one + frame to the next! + + const void* sapp_win32_get_hwnd(void) + On Windows, get the window's HWND, otherwise a null pointer. The + HWND has been cast to a void pointer in order to be tunneled + through code which doesn't include Windows.h. + + const void* sapp_x11_get_window(void) + On Linux, get the X11 Window, otherwise a null pointer. The + Window has been cast to a void pointer in order to be tunneled + through code which doesn't include X11/Xlib.h. + + const void* sapp_x11_get_display(void) + On Linux, get the X11 Display, otherwise a null pointer. The + Display has been cast to a void pointer in order to be tunneled + through code which doesn't include X11/Xlib.h. + + const void* sapp_wgpu_get_device(void) + const void* sapp_wgpu_get_render_view(void) + const void* sapp_wgpu_get_resolve_view(void) + const void* sapp_wgpu_get_depth_stencil_view(void) + These are the WebGPU-specific functions to get the WebGPU + objects and values required for rendering. If sokol_app.h + is not compiled with SOKOL_WGPU, these functions return null. + + uint32_t sapp_gl_get_framebuffer(void) + This returns the 'default framebuffer' of the GL context. + Typically this will be zero. + + int sapp_gl_get_major_version(void) + int sapp_gl_get_minor_version(void) + bool sapp_gl_is_gles(void) + Returns the major and minor version of the GL context and + whether the GL context is a GLES context + + const void* sapp_android_get_native_activity(void); + On Android, get the native activity ANativeActivity pointer, otherwise + a null pointer. + + --- Implement the frame-callback function, this function will be called + on the same thread as the init callback, but might be on a different + thread than the sokol_main() function. Note that the size of + the rendering framebuffer might have changed since the frame callback + was called last. Call the functions sapp_width() and sapp_height() + each frame to get the current size. + + --- Optionally implement the event-callback to handle input events. + sokol-app provides the following type of input events: + - a 'virtual key' was pressed down or released + - a single text character was entered (provided as UTF-32 encoded + UNICODE code point) + - a mouse button was pressed down or released (left, right, middle) + - mouse-wheel or 2D scrolling events + - the mouse was moved + - the mouse has entered or left the application window boundaries + - low-level, portable multi-touch events (began, moved, ended, cancelled) + - the application window was resized, iconified or restored + - the application was suspended or restored (on mobile platforms) + - the user or application code has asked to quit the application + - a string was pasted to the system clipboard + - one or more files have been dropped onto the application window + + To explicitly 'consume' an event and prevent that the event is + forwarded for further handling to the operating system, call + sapp_consume_event() from inside the event handler (NOTE that + this behaviour is currently only implemented for some HTML5 + events, support for other platforms and event types will + be added as needed, please open a GitHub ticket and/or provide + a PR if needed). + + NOTE: Do *not* call any 3D API rendering functions in the event + callback function, since the 3D API context may not be active when the + event callback is called (it may work on some platforms and 3D APIs, + but not others, and the exact behaviour may change between + sokol-app versions). + + --- Implement the cleanup-callback function, this is called once + after the user quits the application (see the section + "APPLICATION QUIT" for detailed information on quitting + behaviour, and how to intercept a pending quit - for instance to show a + "Really Quit?" dialog box). Note that the cleanup-callback isn't + guaranteed to be called on the web and mobile platforms. + + MOUSE CURSOR TYPE AND VISIBILITY + ================================ + You can show and hide the mouse cursor with + + void sapp_show_mouse(bool show) + + And to get the current shown status: + + bool sapp_mouse_shown(void) + + NOTE that hiding the mouse cursor is different and independent from + the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when + active (MOUSE LOCK is described below). + + To change the mouse cursor to one of several predefined types, call + the function: + + void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) + + Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore + the standard look. + + To get the currently active mouse cursor type, call: + + sapp_mouse_cursor sapp_get_mouse_cursor(void) + + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) + ================================================ + In normal mouse mode, no mouse movement events are reported when the + mouse leaves the windows client area or hits the screen border (whether + it's one or the other depends on the platform), and the mouse move events + (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in + framebuffer pixels in the sapp_event items mouse_x and mouse_y, and + relative movement in framebuffer pixels in the sapp_event items mouse_dx + and mouse_dy. + + To get continuous mouse movement (also when the mouse leaves the window + client area or hits the screen border), activate mouse-lock mode + by calling: + + sapp_lock_mouse(true) + + When mouse lock is activated, the mouse pointer is hidden, the + reported absolute mouse position (sapp_event.mouse_x/y) appears + frozen, and the relative mouse movement in sapp_event.mouse_dx/dy + no longer has a direct relation to framebuffer pixels but instead + uses "raw mouse input" (what "raw mouse input" exactly means also + differs by platform). + + To deactivate mouse lock and return to normal mouse mode, call + + sapp_lock_mouse(false) + + And finally, to check if mouse lock is currently active, call + + if (sapp_mouse_locked()) { ... } + + Note that mouse-lock state may not change immediately after sapp_lock_mouse(true/false) + is called, instead on some platforms the actual state switch may be delayed + to the end of the current frame or even to a later frame. + + The mouse may also be unlocked automatically without calling sapp_lock_mouse(false), + most notably when the application window becomes inactive. + + On the web platform there are further restrictions to be aware of, caused + by the limitations of the HTML5 Pointer Lock API: + + - sapp_lock_mouse(true) can be called at any time, but it will + only take effect in a 'short-lived input event handler of a specific + type', meaning when one of the following events happens: + - SAPP_EVENTTYPE_MOUSE_DOWN + - SAPP_EVENTTYPE_MOUSE_UP + - SAPP_EVENTTYPE_MOUSE_SCROLL + - SAPP_EVENTTYPE_KEY_UP + - SAPP_EVENTTYPE_KEY_DOWN + - The mouse lock/unlock action on the web platform is asynchronous, + this means that sapp_mouse_locked() won't immediately return + the new status after calling sapp_lock_mouse(), instead the + reported status will only change when the pointer lock has actually + been activated or deactivated in the browser. + - On the web, mouse lock can be deactivated by the user at any time + by pressing the Esc key. When this happens, sokol_app.h behaves + the same as if sapp_lock_mouse(false) is called. + + For things like camera manipulation it's most straightforward to lock + and unlock the mouse right from the sokol_app.h event handler, for + instance the following code enters and leaves mouse lock when the + left mouse button is pressed and released, and then uses the relative + movement information to manipulate a camera (taken from the + cgltf-sapp.c sample in the sokol-samples repository + at https://github.com/floooh/sokol-samples): + + static void input(const sapp_event* ev) { + switch (ev->type) { + case SAPP_EVENTTYPE_MOUSE_DOWN: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(true); + } + break; + + case SAPP_EVENTTYPE_MOUSE_UP: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(false); + } + break; + + case SAPP_EVENTTYPE_MOUSE_MOVE: + if (sapp_mouse_locked()) { + cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); + } + break; + + default: + break; + } + } + + For a 'first person shooter mouse' the following code inside the sokol-app event handler + is recommended somewhere in your frame callback: + + if (!sapp_mouse_locked()) { + sapp_lock_mouse(true); + } + + CLIPBOARD SUPPORT + ================= + Applications can send and receive UTF-8 encoded text data from and to the + system clipboard. By default, clipboard support is disabled and + must be enabled at startup via the following sapp_desc struct + members: + + sapp_desc.enable_clipboard - set to true to enable clipboard support + sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes + + Enabling the clipboard will dynamically allocate a clipboard buffer + for UTF-8 encoded text data of the requested size in bytes, the default + size is 8 KBytes. Strings that don't fit into the clipboard buffer + (including the terminating zero) will be silently clipped, so it's + important that you provide a big enough clipboard size for your + use case. + + To send data to the clipboard, call sapp_set_clipboard_string() with + a pointer to an UTF-8 encoded, null-terminated C-string. + + NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be + called from inside a 'short-lived event handler', and there are a few + other HTML5-specific caveats to workaround. You'll basically have to + tinker until it works in all browsers :/ (maybe the situation will + improve when all browsers agree on and implement the new + HTML5 navigator.clipboard API). + + To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED + event in your event handler function, and then call sapp_get_clipboard_string() + to obtain the pasted UTF-8 encoded text. + + NOTE that behaviour of sapp_get_clipboard_string() is slightly different + depending on platform: + + - on the HTML5 platform, the internal clipboard buffer will only be updated + right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, + and sapp_get_clipboard_string() will simply return the current content + of the clipboard buffer + - on 'native' platforms, the call to sapp_get_clipboard_string() will + update the internal clipboard buffer with the most recent data + from the system clipboard + + Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, + and then call sapp_get_clipboard_string() right in the event handler. + + The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app + as follows: + + - on macOS: when the Cmd+V key is pressed down + - on HTML5: when the browser sends a 'paste' event to the global 'window' object + - on all other platforms: when the Ctrl+V key is pressed down + + DRAG AND DROP SUPPORT + ===================== + PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 + and on the native desktop platforms (Win32, Linux and macOS) because + of security-related restrictions in the HTML5 drag'n'drop API. The + WASM/HTML5 specifics are described at the end of this documentation + section: + + Like clipboard support, drag'n'drop support must be explicitly enabled + at startup in the sapp_desc struct. + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + ... + }; + } + + You can also adjust the maximum number of files that are accepted + in a drop operation, and the maximum path length in bytes if needed: + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + .max_dropped_files = 8, // default is 1 + .max_dropped_file_path_length = 8192, // in bytes, default is 2048 + ... + }; + } + + When drag'n'drop is enabled, the event callback will be invoked with an + event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on + the application window. + + After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the + number of dropped files, and their absolute paths by calling separate + functions: + + void on_event(const sapp_event* ev) { + if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { + + // the mouse position where the drop happened + float x = ev->mouse_x; + float y = ev->mouse_y; + + // get the number of files and their paths like this: + const int num_dropped_files = sapp_get_num_dropped_files(); + for (int i = 0; i < num_dropped_files; i++) { + const char* path = sapp_get_dropped_file_path(i); + ... + } + } + } + + The returned file paths are UTF-8 encoded strings. + + You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() + anywhere, also outside the event handler callback, but be aware that the + file path strings will be overwritten with the next drop operation. + + In any case, sapp_get_dropped_file_path() will never return a null pointer, + instead an empty string "" will be returned if the drag'n'drop feature + hasn't been enabled, the last drop-operation failed, or the file path index + is out of range. + + Drag'n'drop caveats: + + - if more files are dropped in a single drop-action + than sapp_desc.max_dropped_files, the additional + files will be silently ignored + - if any of the file paths is longer than + sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 + encoding) the entire drop operation will be silently ignored (this + needs some sort of error feedback in the future) + - no mouse positions are reported while the drag is in + process, this may change in the future + + Drag'n'drop on HTML5/WASM: + + The HTML5 drag'n'drop API doesn't return file paths, but instead + black-box 'file objects' which must be used to load the content + of dropped files. This is the reason why sokol_app.h adds two + HTML5-specific functions to the drag'n'drop API: + + uint32_t sapp_html5_get_dropped_file_size(int index) + Returns the size in bytes of a dropped file. + + void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) + Asynchronously loads the content of a dropped file into a + provided memory buffer (which must be big enough to hold + the file content) + + To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED + event is received: + + sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ + .dropped_file_index = 0, + .callback = fetch_cb + .buffer = { + .ptr = buf, + .size = sizeof(buf) + }, + .user_data = ... + }); + + Make sure that the memory pointed to by 'buf' stays valid until the + callback function is called! + + As result of the asynchronous loading operation (no matter if succeeded or + failed) the 'fetch_cb' function will be called: + + void fetch_cb(const sapp_html5_fetch_response* response) { + // IMPORTANT: check if the loading operation actually succeeded: + if (response->succeeded) { + // the size of the loaded file: + const size_t num_bytes = response->data.size; + // and the pointer to the data (same as 'buf' in the fetch-call): + const void* ptr = response->data.ptr; + } + else { + // on error check the error code: + switch (response->error_code) { + case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: + ... + break; + case SAPP_HTML5_FETCH_ERROR_OTHER: + ... + break; + } + } + } + + Check the droptest-sapp example for a real-world example which works + both on native platforms and the web: + + https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c + + HIGH-DPI RENDERING + ================== + You can set the sapp_desc.high_dpi flag during initialization to request + a full-resolution framebuffer on HighDPI displays. The default behaviour + is sapp_desc.high_dpi=false, this means that the application will + render to a lower-resolution framebuffer on HighDPI displays and the + rendered content will be upscaled by the window system composer. + + In a HighDPI scenario, you still request the same window size during + sokol_main(), but the framebuffer sizes returned by sapp_width() + and sapp_height() will be scaled up according to the DPI scaling + ratio. + + Note that on some platforms the DPI scaling factor may change at any + time (for instance when a window is moved from a high-dpi display + to a low-dpi display). + + To query the current DPI scaling factor, call the function: + + float sapp_dpi_scale(void); + + For instance on a Retina Mac, returning the following sapp_desc + struct from sokol_main(): + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .width = 640, + .height = 480, + .high_dpi = true, + ... + }; + } + + ...the functions the functions sapp_width(), sapp_height() + and sapp_dpi_scale() will return the following values: + + sapp_width: 1280 + sapp_height: 960 + sapp_dpi_scale: 2.0 + + If the high_dpi flag is false, or you're not running on a Retina display, + the values would be: + + sapp_width: 640 + sapp_height: 480 + sapp_dpi_scale: 1.0 + + If the window is moved from the Retina display to a low-dpi external display, + the values would change as follows: + + sapp_width: 1280 => 640 + sapp_height: 960 => 480 + sapp_dpi_scale: 2.0 => 1.0 + + Currently there is no event associated with a DPI change, but an + SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the + framebuffer size changing. + + Per-monitor DPI is currently supported on macOS and Windows. + + APPLICATION QUIT + ================ + Without special quit handling, a sokol_app.h application will quit + 'gracefully' when the user clicks the window close-button unless a + platform's application model prevents this (e.g. on web or mobile). + 'Graceful exit' means that the application-provided cleanup callback will + be called before the application quits. + + On native desktop platforms sokol_app.h provides more control over the + application-quit-process. It's possible to initiate a 'programmatic quit' + from the application code, and a quit initiated by the application user can + be intercepted (for instance to show a custom dialog box). + + This 'programmatic quit protocol' is implemented through 3 functions + and 1 event: + + - sapp_quit(): This function simply quits the application without + giving the user a chance to intervene. Usually this might + be called when the user clicks the 'Ok' button in a 'Really Quit?' + dialog box + - sapp_request_quit(): Calling sapp_request_quit() will send the + event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler + callback, giving the user code a chance to intervene and cancel the + pending quit process (for instance to show a 'Really Quit?' dialog + box). If the event handler callback does nothing, the application + will be quit as usual. To prevent this, call the function + sapp_cancel_quit() from inside the event handler. + - sapp_cancel_quit(): Cancels a pending quit request, either initiated + by the user clicking the window close button, or programmatically + by calling sapp_request_quit(). The only place where calling this + function makes sense is from inside the event handler callback when + the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. + - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user + clicks the window's close button or application code calls the + sapp_request_quit() function. The event handler callback code can handle + this event by calling sapp_cancel_quit() to cancel the quit. + If the event is ignored, the application will quit as usual. + + On the web platform, the quit behaviour differs from native platforms, + because of web-specific restrictions: + + A `programmatic quit` initiated by calling sapp_quit() or + sapp_request_quit() will work as described above: the cleanup callback is + called, platform-specific cleanup is performed (on the web + this means that JS event handlers are unregistered), and then + the request-animation-loop will be exited. However that's all. The + web page itself will continue to exist (e.g. it's not possible to + programmatically close the browser tab). + + On the web it's also not possible to run custom code when the user + closes a browser tab, so it's not possible to prevent this with a + fancy custom dialog box. + + Instead the standard "Leave Site?" dialog box can be activated (or + deactivated) with the following function: + + sapp_html5_ask_leave_site(bool ask); + + The initial state of the associated internal flag can be provided + at startup via sapp_desc.html5_ask_leave_site. + + This feature should only be used sparingly in critical situations - for + instance when the user would loose data - since popping up modal dialog + boxes is considered quite rude in the web world. Note that there's no way + to customize the content of this dialog box or run any code as a result + of the user's decision. Also note that the user must have interacted with + the site before the dialog box will appear. These are all security measures + to prevent fishing. + + The Dear ImGui HighDPI sample contains example code of how to + implement a 'Really Quit?' dialog box with Dear ImGui (native desktop + platforms only), and for showing the hardwired "Leave Site?" dialog box + when running on the web platform: + + https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html + + FULLSCREEN + ========== + If the sapp_desc.fullscreen flag is true, sokol-app will try to create + a fullscreen window on platforms with a 'proper' window system + (mobile devices will always use fullscreen). The implementation details + depend on the target platform, in general sokol-app will use a + 'soft approach' which doesn't interfere too much with the platform's + window system (for instance borderless fullscreen window instead of + a 'real' fullscreen mode). Such details might change over time + as sokol-app is adapted for different needs. + + The most important effect of fullscreen mode to keep in mind is that + the requested canvas width and height will be ignored for the initial + window size, calling sapp_width() and sapp_height() will instead return + the resolution of the fullscreen canvas (however the provided size + might still be used for the non-fullscreen window, in case the user can + switch back from fullscreen- to windowed-mode). + + To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). + + To check if the application window is currently in fullscreen mode, + call sapp_is_fullscreen(). + + WINDOW ICON SUPPORT + =================== + Some sokol_app.h backends allow to change the window icon programmatically: + + - on Win32: the small icon in the window's title bar, and the + bigger icon in the task bar + - on Linux: highly dependent on the used window manager, but usually + the window's title bar icon and/or the task bar icon + - on HTML5: the favicon shown in the page's browser tab + - on macOS: the application icon shown in the dock, but only + for currently running applications + + NOTE that it is not possible to set the actual application icon which is + displayed by the operating system on the desktop or 'home screen'. Those + icons must be provided 'traditionally' through operating-system-specific + resources which are associated with the application (sokol_app.h might + later support setting the window icon from platform specific resource data + though). + + There are two ways to set the window icon: + + - at application start in the sokol_main() function by initializing + the sapp_desc.icon nested struct + - or later by calling the function sapp_set_icon() + + As a convenient shortcut, sokol_app.h comes with a builtin default-icon + (a rainbow-colored 'S', which at least looks a bit better than the Windows + default icon for applications), which can be activated like this: + + At startup in sokol_main(): + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon.sokol_default = true + }; + } + + Or later by calling: + + sapp_set_icon(&(sapp_icon_desc){ .sokol_default = true }); + + NOTE that a completely zero-initialized sapp_icon_desc struct will not + update the window icon in any way. This is an 'escape hatch' so that you + can handle the window icon update yourself (or if you do this already, + sokol_app.h won't get in your way, in this case just leave the + sapp_desc.icon struct zero-initialized). + + Providing your own icon images works exactly like in GLFW (down to the + data format): + + You provide one or more 'candidate images' in different sizes, and the + sokol_app.h platform backends pick the best match for the specific backend + and icon type. + + For each candidate image, you need to provide: + + - the width in pixels + - the height in pixels + - and the actual pixel data in RGBA8 pixel format (e.g. 0xFFCC8844 + on a little-endian CPU means: alpha=0xFF, blue=0xCC, green=0x88, red=0x44) + + For instance, if you have 3 candidate images (small, medium, big) of + sizes 16x16, 32x32 and 64x64 the corresponding sapp_icon_desc struct is setup + like this: + + // the actual pixel data (RGBA8, origin top-left) + const uint32_t small[16][16] = { ... }; + const uint32_t medium[32][32] = { ... }; + const uint32_t big[64][64] = { ... }; + + const sapp_icon_desc icon_desc = { + .images = { + { .width = 16, .height = 16, .pixels = SAPP_RANGE(small) }, + { .width = 32, .height = 32, .pixels = SAPP_RANGE(medium) }, + // ...or without the SAPP_RANGE helper macro: + { .width = 64, .height = 64, .pixels = { .ptr=big, .size=sizeof(big) } } + } + }; + + An sapp_icon_desc struct initialized like this can then either be applied + at application start in sokol_main: + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon = icon_desc + }; + } + + ...or later by calling sapp_set_icon(): + + sapp_set_icon(&icon_desc); + + Some window icon caveats: + + - once the window icon has been updated, there's no way to go back to + the platform's default icon, this is because some platforms (Linux + and HTML5) don't switch the icon visual back to the default even if + the custom icon is deleted or removed + - on HTML5, if the sokol_app.h icon doesn't show up in the browser + tab, check that there's no traditional favicon 'link' element + is defined in the page's index.html, sokol_app.h will only + append a new favicon link element, but not delete any manually + defined favicon in the page + + For an example and test of the window icon feature, check out the + 'icon-sapp' sample on the sokol-samples git repository. + + ONSCREEN KEYBOARD + ================= + On some platforms which don't provide a physical keyboard, sokol-app + can display the platform's integrated onscreen keyboard for text + input. To request that the onscreen keyboard is shown, call + + sapp_show_keyboard(true); + + Likewise, to hide the keyboard call: + + sapp_show_keyboard(false); + + Note that onscreen keyboard functionality is no longer supported + on the browser platform (the previous hacks and workarounds to make browser + keyboards work for on web applications that don't use HTML UIs + never really worked across browsers). + + INPUT EVENT BUBBLING ON THE WEB PLATFORM + ======================================== + By default, input event bubbling on the web platform is configured in + a way that makes the most sense for 'full-canvas' apps that cover the + entire browser client window area: + + - mouse, touch and wheel events do not bubble up, this prevents various + ugly side events, like: + - HTML text overlays being selected on double- or triple-click into + the canvas + - 'scroll bumping' even when the canvas covers the entire client area + - key_up/down events for 'character keys' *do* bubble up (otherwise + the browser will not generate UNICODE character events) + - all other key events *do not* bubble up by default (this prevents side effects + like F1 opening help, or F7 starting 'caret browsing') + - character events do not bubble up (although I haven't noticed any side effects + otherwise) + + Event bubbling can be enabled for input event categories during initialization + in the sapp_desc struct: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + //... + .html5_bubble_mouse_events = true, + .html5_bubble_touch_events = true, + .html5_bubble_wheel_events = true, + .html5_bubble_key_events = true, + .html5_bubble_char_events = true, + }; + } + + This basically opens the floodgates and lets *all* input events bubble up to the browser. + + To prevent individual events from bubbling, call sapp_consume_event() from within + the sokol_app.h event callback when that specific event is reported. + + + SETTING THE CANVAS OBJECT ON THE WEB PLATFORM + ============================================= + On the web, sokol_app.h and the Emscripten SDK functions need to find + the WebGL/WebGPU canvas intended for rendering and attaching event + handlers. This can happen in four ways: + + 1. do nothing and just set the id of the canvas object to 'canvas' (preferred) + 2. via a CSS Selector string (preferred) + 3. by setting the `Module.canvas` property to the canvas object + 4. by adding the canvas object to the global variable `specialHTMLTargets[]` + (this is a special variable used by the Emscripten runtime to lookup + event target objects for which document.querySelector() cannot be used) + + The easiest way is to just name your canvas object 'canvas': + + + + This works because the default css selector string used by sokol_app.h + is '#canvas'. + + If you name your canvas differently, you need to communicate that name to + sokol_app.h via `sapp_desc.html5_canvas_selector` as a regular css selector + string that's compatible with `document.querySelector()`. E.g. if your canvas + object looks like this: + + + + The `sapp_desc.html5_canvas_selector` string must be set to '#bla': + + .html5_canvas_selector = "#bla" + + If the canvas object cannot be looked up via `document.querySelector()` you + need to use one of the alternative methods, both involve the special + Emscripten runtime `Module` object which is usually setup in the index.html + like this before the WASM blob is loaded and instantiated: + + + + The first option is to set the `Module.canvas` property to your canvas object: + + + + When sokol_app.h initializes, it will check the global Module object whether + a `Module.canvas` property exists and is an object. This method will add + a new entry to the `specialHTMLTargets[]` object + + The other option is to add the canvas under a name chosen by you to the + special `specialHTMLTargets[]` map, which is used by the Emscripten runtime + to lookup 'event target objects' which are not visible to `document.querySelector()`. + Note that `specialHTMLTargets[]` must be updated after the Emscripten runtime + has started but before the WASM code is running. A good place for this is + the special `Module.preRun` array in index.html: + + + + In that case, pass the same string to sokol_app.h which is used as key + in the specialHTMLTargets[] map: + + .html5_canvas_selector = "my_canvas" + + If sokol_app.h can't find your canvas for some reason check for warning + messages on the browser console. + + + OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) + ====================================================== + NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android. + + In its default configuration, sokol_app.h "hijacks" the platform's + standard main() function. This was done because different platforms + have different entry point conventions which are not compatible with + C's main() (for instance WinMain on Windows has completely different + arguments). However, this "main hijacking" posed a problem for + usage scenarios like integrating sokol_app.h with other languages than + C or C++, so an alternative SOKOL_NO_ENTRY mode has been added + in which the user code provides the platform's main function: + + - define SOKOL_NO_ENTRY before including the sokol_app.h implementation + - do *not* provide a sokol_main() function + - instead provide the standard main() function of the platform + - from the main function, call the function ```sapp_run()``` which + takes a pointer to an ```sapp_desc``` structure. + - from here on```sapp_run()``` takes over control and calls the provided + init-, frame-, event- and cleanup-callbacks just like in the default model. + + sapp_run() behaves differently across platforms: + + - on some platforms, sapp_run() will return when the application quits + - on other platforms, sapp_run() will never return, even when the + application quits (the operating system is free to simply terminate + the application at any time) + - on Emscripten specifically, sapp_run() will return immediately while + the frame callback keeps being called + + This different behaviour of sapp_run() essentially means that there shouldn't + be any code *after* sapp_run(), because that may either never be called, or in + case of Emscripten will be called at an unexpected time (at application start). + + An application also should not depend on the cleanup-callback being called + when cross-platform compatibility is required. + + Since sapp_run() returns immediately on Emscripten you shouldn't activate + the 'EXIT_RUNTIME' linker option (this is disabled by default when compiling + for the browser target), since the C/C++ exit runtime would be called immediately at + application start, causing any global objects to be destroyed and global + variables to be zeroed. + + WINDOWS CONSOLE OUTPUT + ====================== + On Windows, regular windowed applications don't show any stdout/stderr text + output, which can be a bit of a hassle for printf() debugging or generally + logging text to the console. Also, console output by default uses a local + codepage setting and thus international UTF-8 encoded text is printed + as garbage. + + To help with these issues, sokol_app.h can be configured at startup + via the following Windows-specific sapp_desc flags: + + sapp_desc.win32_console_utf8 (default: false) + When set to true, the output console codepage will be switched + to UTF-8 (and restored to the original codepage on exit) + + sapp_desc.win32_console_attach (default: false) + When set to true, stdout and stderr will be attached to the + console of the parent process (if the parent process actually + has a console). This means that if the application was started + in a command line window, stdout and stderr output will be printed + to the terminal, just like a regular command line program. But if + the application is started via double-click, it will behave like + a regular UI application, and stdout/stderr will not be visible. + + sapp_desc.win32_console_create (default: false) + When set to true, a new console window will be created and + stdout/stderr will be redirected to that console window. It + doesn't matter if the application is started from the command + line or via double-click. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }; + } + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_app.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sapp' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-app like this: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }; + } + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + TEMP NOTE DUMP + ============== + - sapp_desc needs a bool whether to initialize depth-stencil surface + - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy + at the latest but should do it earlier, in onStop, as an app is "killable" after onStop + on Android Honeycomb and later (it can't be done at the moment as the app may be started + again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; + #if DEBUG { sokol_app_clib :: #library "sokol_app_windows_x64_gl_debug"; } + else { sokol_app_clib :: #library "sokol_app_windows_x64_gl_release"; } + } else { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; + #if DEBUG { sokol_app_clib :: #library "sokol_app_windows_x64_d3d11_debug"; } + else { sokol_app_clib :: #library "sokol_app_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_windows_x64_gl_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_windows_x64_gl_release"; } + } else { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_windows_x64_d3d11_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_app_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; + #if CPU == .ARM64 { + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_macos_arm64_gl_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_macos_x64_gl_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_macos_x64_gl_release"; } + } + } else { + #library "../../libclang_rt.osx"; #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; + #if CPU == .ARM64 { + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_macos_arm64_metal_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_macos_x64_metal_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #system_library,link_always "libXcursor"; #system_library,link_always "libX11"; #system_library,link_always "libXi"; #system_library,link_always "libGL"; + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_linux_x64_gl_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_app_clib :: #library,no_dll "sokol_app_wasm_gl_debug"; } + else { sokol_app_clib :: #library,no_dll "sokol_app_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// returns true after sokol-app has been initialized +sapp_isvalid :: () -> bool #foreign sokol_app_clib; +// returns the current framebuffer width in pixels +sapp_width :: () -> s32 #foreign sokol_app_clib; +// same as sapp_width(), but returns float +sapp_widthf :: () -> float #foreign sokol_app_clib; +// returns the current framebuffer height in pixels +sapp_height :: () -> s32 #foreign sokol_app_clib; +// same as sapp_height(), but returns float +sapp_heightf :: () -> float #foreign sokol_app_clib; +// get default framebuffer color pixel format +sapp_color_format :: () -> s32 #foreign sokol_app_clib; +// get default framebuffer depth pixel format +sapp_depth_format :: () -> s32 #foreign sokol_app_clib; +// get default framebuffer sample count +sapp_sample_count :: () -> s32 #foreign sokol_app_clib; +// returns true when high_dpi was requested and actually running in a high-dpi scenario +sapp_high_dpi :: () -> bool #foreign sokol_app_clib; +// returns the dpi scaling factor (window pixels to framebuffer pixels) +sapp_dpi_scale :: () -> float #foreign sokol_app_clib; +// show or hide the mobile device onscreen keyboard +sapp_show_keyboard :: (show: bool) -> void #foreign sokol_app_clib; +// return true if the mobile device onscreen keyboard is currently shown +sapp_keyboard_shown :: () -> bool #foreign sokol_app_clib; +// query fullscreen mode +sapp_is_fullscreen :: () -> bool #foreign sokol_app_clib; +// toggle fullscreen mode +sapp_toggle_fullscreen :: () -> void #foreign sokol_app_clib; +// show or hide the mouse cursor +sapp_show_mouse :: (show: bool) -> void #foreign sokol_app_clib; +// show or hide the mouse cursor +sapp_mouse_shown :: () -> bool #foreign sokol_app_clib; +// enable/disable mouse-pointer-lock mode +sapp_lock_mouse :: (lock: bool) -> void #foreign sokol_app_clib; +// return true if in mouse-pointer-lock mode (this may toggle a few frames later) +sapp_mouse_locked :: () -> bool #foreign sokol_app_clib; +// set mouse cursor type +sapp_set_mouse_cursor :: (cursor: sapp_mouse_cursor) -> void #foreign sokol_app_clib; +// get current mouse cursor type +sapp_get_mouse_cursor :: () -> sapp_mouse_cursor #foreign sokol_app_clib; +// return the userdata pointer optionally provided in sapp_desc +sapp_userdata :: () -> *void #foreign sokol_app_clib; +// return a copy of the sapp_desc structure +sapp_query_desc :: () -> sapp_desc #foreign sokol_app_clib; +// initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) +sapp_request_quit :: () -> void #foreign sokol_app_clib; +// cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) +sapp_cancel_quit :: () -> void #foreign sokol_app_clib; +// initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUESTED) +sapp_quit :: () -> void #foreign sokol_app_clib; +// call from inside event callback to consume the current event (don't forward to platform) +sapp_consume_event :: () -> void #foreign sokol_app_clib; +// get the current frame counter (for comparison with sapp_event.frame_count) +sapp_frame_count :: () -> u64 #foreign sokol_app_clib; +// get an averaged/smoothed frame duration in seconds +sapp_frame_duration :: () -> float64 #foreign sokol_app_clib; +// write string into clipboard +sapp_set_clipboard_string :: (str: *u8) -> void #foreign sokol_app_clib; +// read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) +sapp_get_clipboard_string :: () -> *u8 #foreign sokol_app_clib; +// set the window title (only on desktop platforms) +sapp_set_window_title :: (str: *u8) -> void #foreign sokol_app_clib; +// set the window icon (only on Windows and Linux) +sapp_set_icon :: (icon_desc: *sapp_icon_desc) -> void #foreign sokol_app_clib; +// gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) +sapp_get_num_dropped_files :: () -> s32 #foreign sokol_app_clib; +// gets the dropped file paths +sapp_get_dropped_file_path :: (index: s32) -> *u8 #foreign sokol_app_clib; +// special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) +sapp_run :: (desc: *sapp_desc) -> void #foreign sokol_app_clib; +// EGL: get EGLDisplay object +sapp_egl_get_display :: () -> *void #foreign sokol_app_clib; +// EGL: get EGLContext object +sapp_egl_get_context :: () -> *void #foreign sokol_app_clib; +// HTML5: enable or disable the hardwired "Leave Site?" dialog box +sapp_html5_ask_leave_site :: (ask: bool) -> void #foreign sokol_app_clib; +// HTML5: get byte size of a dropped file +sapp_html5_get_dropped_file_size :: (index: s32) -> u32 #foreign sokol_app_clib; +// HTML5: asynchronously load the content of a dropped file +sapp_html5_fetch_dropped_file :: (request: *sapp_html5_fetch_request) -> void #foreign sokol_app_clib; +// Metal: get bridged pointer to Metal device object +sapp_metal_get_device :: () -> *void #foreign sokol_app_clib; +// Metal: get bridged pointer to MTKView's current drawable of type CAMetalDrawable +sapp_metal_get_current_drawable :: () -> *void #foreign sokol_app_clib; +// Metal: get bridged pointer to MTKView's depth-stencil texture of type MTLTexture +sapp_metal_get_depth_stencil_texture :: () -> *void #foreign sokol_app_clib; +// Metal: get bridged pointer to MTKView's msaa-color-texture of type MTLTexture (may be null) +sapp_metal_get_msaa_color_texture :: () -> *void #foreign sokol_app_clib; +// macOS: get bridged pointer to macOS NSWindow +sapp_macos_get_window :: () -> *void #foreign sokol_app_clib; +// iOS: get bridged pointer to iOS UIWindow +sapp_ios_get_window :: () -> *void #foreign sokol_app_clib; +// D3D11: get pointer to ID3D11Device object +sapp_d3d11_get_device :: () -> *void #foreign sokol_app_clib; +// D3D11: get pointer to ID3D11DeviceContext object +sapp_d3d11_get_device_context :: () -> *void #foreign sokol_app_clib; +// D3D11: get pointer to IDXGISwapChain object +sapp_d3d11_get_swap_chain :: () -> *void #foreign sokol_app_clib; +// D3D11: get pointer to ID3D11RenderTargetView object for rendering +sapp_d3d11_get_render_view :: () -> *void #foreign sokol_app_clib; +// D3D11: get pointer ID3D11RenderTargetView object for msaa-resolve (may return null) +sapp_d3d11_get_resolve_view :: () -> *void #foreign sokol_app_clib; +// D3D11: get pointer ID3D11DepthStencilView +sapp_d3d11_get_depth_stencil_view :: () -> *void #foreign sokol_app_clib; +// Win32: get the HWND window handle +sapp_win32_get_hwnd :: () -> *void #foreign sokol_app_clib; +// WebGPU: get WGPUDevice handle +sapp_wgpu_get_device :: () -> *void #foreign sokol_app_clib; +// WebGPU: get swapchain's WGPUTextureView handle for rendering +sapp_wgpu_get_render_view :: () -> *void #foreign sokol_app_clib; +// WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) +sapp_wgpu_get_resolve_view :: () -> *void #foreign sokol_app_clib; +// WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface +sapp_wgpu_get_depth_stencil_view :: () -> *void #foreign sokol_app_clib; +// GL: get framebuffer object +sapp_gl_get_framebuffer :: () -> u32 #foreign sokol_app_clib; +// GL: get major version +sapp_gl_get_major_version :: () -> s32 #foreign sokol_app_clib; +// GL: get minor version +sapp_gl_get_minor_version :: () -> s32 #foreign sokol_app_clib; +// GL: return true if the context is GLES +sapp_gl_is_gles :: () -> bool #foreign sokol_app_clib; +// X11: get Window +sapp_x11_get_window :: () -> *void #foreign sokol_app_clib; +// X11: get Display +sapp_x11_get_display :: () -> *void #foreign sokol_app_clib; +// Android: get native activity handle +sapp_android_get_native_activity :: () -> *void #foreign sokol_app_clib; + +MAX_TOUCHPOINTS :: 8; +MAX_MOUSEBUTTONS :: 3; +MAX_KEYCODES :: 512; +MAX_ICONIMAGES :: 8; + +/* + sapp_event_type + + The type of event that's passed to the event handler callback + in the sapp_event.type field. These are not just "traditional" + input events, but also notify the application about state changes + or other user-invoked actions. +*/ +sapp_event_type :: enum u32 { + INVALID; + KEY_DOWN; + KEY_UP; + CHAR; + MOUSE_DOWN; + MOUSE_UP; + MOUSE_SCROLL; + MOUSE_MOVE; + MOUSE_ENTER; + MOUSE_LEAVE; + TOUCHES_BEGAN; + TOUCHES_MOVED; + TOUCHES_ENDED; + TOUCHES_CANCELLED; + RESIZED; + ICONIFIED; + RESTORED; + FOCUSED; + UNFOCUSED; + SUSPENDED; + RESUMED; + QUIT_REQUESTED; + CLIPBOARD_PASTED; + FILES_DROPPED; +} + +/* + sapp_keycode + + The 'virtual keycode' of a KEY_DOWN or KEY_UP event in the + struct field sapp_event.key_code. + + Note that the keycode values are identical with GLFW. +*/ +sapp_keycode :: enum u32 { + INVALID :: 0; + SPACE :: 32; + APOSTROPHE :: 39; + COMMA :: 44; + MINUS :: 45; + PERIOD :: 46; + SLASH :: 47; + _0 :: 48; + _1 :: 49; + _2 :: 50; + _3 :: 51; + _4 :: 52; + _5 :: 53; + _6 :: 54; + _7 :: 55; + _8 :: 56; + _9 :: 57; + SEMICOLON :: 59; + EQUAL :: 61; + A :: 65; + B :: 66; + C :: 67; + D :: 68; + E :: 69; + F :: 70; + G :: 71; + H :: 72; + I :: 73; + J :: 74; + K :: 75; + L :: 76; + M :: 77; + N :: 78; + O :: 79; + P :: 80; + Q :: 81; + R :: 82; + S :: 83; + T :: 84; + U :: 85; + V :: 86; + W :: 87; + X :: 88; + Y :: 89; + Z :: 90; + LEFT_BRACKET :: 91; + BACKSLASH :: 92; + RIGHT_BRACKET :: 93; + GRAVE_ACCENT :: 96; + WORLD_1 :: 161; + WORLD_2 :: 162; + ESCAPE :: 256; + ENTER :: 257; + TAB :: 258; + BACKSPACE :: 259; + INSERT :: 260; + DELETE :: 261; + RIGHT :: 262; + LEFT :: 263; + DOWN :: 264; + UP :: 265; + PAGE_UP :: 266; + PAGE_DOWN :: 267; + HOME :: 268; + END :: 269; + CAPS_LOCK :: 280; + SCROLL_LOCK :: 281; + NUM_LOCK :: 282; + PRINT_SCREEN :: 283; + PAUSE :: 284; + F1 :: 290; + F2 :: 291; + F3 :: 292; + F4 :: 293; + F5 :: 294; + F6 :: 295; + F7 :: 296; + F8 :: 297; + F9 :: 298; + F10 :: 299; + F11 :: 300; + F12 :: 301; + F13 :: 302; + F14 :: 303; + F15 :: 304; + F16 :: 305; + F17 :: 306; + F18 :: 307; + F19 :: 308; + F20 :: 309; + F21 :: 310; + F22 :: 311; + F23 :: 312; + F24 :: 313; + F25 :: 314; + KP_0 :: 320; + KP_1 :: 321; + KP_2 :: 322; + KP_3 :: 323; + KP_4 :: 324; + KP_5 :: 325; + KP_6 :: 326; + KP_7 :: 327; + KP_8 :: 328; + KP_9 :: 329; + KP_DECIMAL :: 330; + KP_DIVIDE :: 331; + KP_MULTIPLY :: 332; + KP_SUBTRACT :: 333; + KP_ADD :: 334; + KP_ENTER :: 335; + KP_EQUAL :: 336; + LEFT_SHIFT :: 340; + LEFT_CONTROL :: 341; + LEFT_ALT :: 342; + LEFT_SUPER :: 343; + RIGHT_SHIFT :: 344; + RIGHT_CONTROL :: 345; + RIGHT_ALT :: 346; + RIGHT_SUPER :: 347; + MENU :: 348; +} + +/* + Android specific 'tool type' enum for touch events. This lets the + application check what type of input device was used for + touch events. + + NOTE: the values must remain in sync with the corresponding + Android SDK type, so don't change those. + + See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN +*/ +sapp_android_tooltype :: enum u32 { + UNKNOWN :: 0; + FINGER :: 1; + STYLUS :: 2; + MOUSE :: 3; +} + +sapp_touchpoint :: struct { + identifier : u64; + pos_x : float; + pos_y : float; + android_tooltype : sapp_android_tooltype; + changed : bool; +} + +/* + sapp_mousebutton + + The currently pressed mouse button in the events MOUSE_DOWN + and MOUSE_UP, stored in the struct field sapp_event.mouse_button. +*/ +sapp_mousebutton :: enum u32 { + LEFT :: 0; + RIGHT :: 1; + MIDDLE :: 2; + INVALID :: 256; +} + +MODIFIER_SHIFT :: 1; +MODIFIER_CTRL :: 2; +MODIFIER_ALT :: 4; +MODIFIER_SUPER :: 8; +MODIFIER_LMB :: 256; +MODIFIER_RMB :: 512; +MODIFIER_MMB :: 1024; + +sapp_event :: struct { + frame_count : u64; + type : sapp_event_type; + key_code : sapp_keycode; + char_code : u32; + key_repeat : bool; + modifiers : u32; + mouse_button : sapp_mousebutton; + mouse_x : float; + mouse_y : float; + mouse_dx : float; + mouse_dy : float; + scroll_x : float; + scroll_y : float; + num_touches : s32; + touches : [8]sapp_touchpoint; + window_width : s32; + window_height : s32; + framebuffer_width : s32; + framebuffer_height : s32; +} + +sapp_range :: struct { + ptr : *void; + size : u64; +} + +sapp_image_desc :: struct { + width : s32; + height : s32; + pixels : sapp_range; +} + +sapp_icon_desc :: struct { + sokol_default : bool; + images : [8]sapp_image_desc; +} + +sapp_allocator :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +sapp_log_item :: enum u32 { + OK; + MALLOC_FAILED; + MACOS_INVALID_NSOPENGL_PROFILE; + WIN32_LOAD_OPENGL32_DLL_FAILED; + WIN32_CREATE_HELPER_WINDOW_FAILED; + WIN32_HELPER_WINDOW_GETDC_FAILED; + WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED; + WIN32_CREATE_DUMMY_CONTEXT_FAILED; + WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED; + WIN32_GET_PIXELFORMAT_ATTRIB_FAILED; + WIN32_WGL_FIND_PIXELFORMAT_FAILED; + WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED; + WIN32_WGL_SET_PIXELFORMAT_FAILED; + WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED; + WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED; + WIN32_WGL_OPENGL_VERSION_NOT_SUPPORTED; + WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED; + WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT; + WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER; + WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED; + WIN32_D3D11_GET_IDXGIFACTORY_FAILED; + WIN32_D3D11_GET_IDXGIADAPTER_FAILED; + WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED; + WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK; + WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK; + WIN32_GET_RAW_INPUT_DATA_FAILED; + LINUX_GLX_LOAD_LIBGL_FAILED; + LINUX_GLX_LOAD_ENTRY_POINTS_FAILED; + LINUX_GLX_EXTENSION_NOT_FOUND; + LINUX_GLX_QUERY_VERSION_FAILED; + LINUX_GLX_VERSION_TOO_LOW; + LINUX_GLX_NO_GLXFBCONFIGS; + LINUX_GLX_NO_SUITABLE_GLXFBCONFIG; + LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED; + LINUX_GLX_REQUIRED_EXTENSIONS_MISSING; + LINUX_GLX_CREATE_CONTEXT_FAILED; + LINUX_GLX_CREATE_WINDOW_FAILED; + LINUX_X11_CREATE_WINDOW_FAILED; + LINUX_EGL_BIND_OPENGL_API_FAILED; + LINUX_EGL_BIND_OPENGL_ES_API_FAILED; + LINUX_EGL_GET_DISPLAY_FAILED; + LINUX_EGL_INITIALIZE_FAILED; + LINUX_EGL_NO_CONFIGS; + LINUX_EGL_NO_NATIVE_VISUAL; + LINUX_EGL_GET_VISUAL_INFO_FAILED; + LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED; + LINUX_EGL_CREATE_CONTEXT_FAILED; + LINUX_EGL_MAKE_CURRENT_FAILED; + LINUX_X11_OPEN_DISPLAY_FAILED; + LINUX_X11_QUERY_SYSTEM_DPI_FAILED; + LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME; + LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD; + ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB; + ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB; + ANDROID_READ_MSG_FAILED; + ANDROID_WRITE_MSG_FAILED; + ANDROID_MSG_CREATE; + ANDROID_MSG_RESUME; + ANDROID_MSG_PAUSE; + ANDROID_MSG_FOCUS; + ANDROID_MSG_NO_FOCUS; + ANDROID_MSG_SET_NATIVE_WINDOW; + ANDROID_MSG_SET_INPUT_QUEUE; + ANDROID_MSG_DESTROY; + ANDROID_UNKNOWN_MSG; + ANDROID_LOOP_THREAD_STARTED; + ANDROID_LOOP_THREAD_DONE; + ANDROID_NATIVE_ACTIVITY_ONSTART; + ANDROID_NATIVE_ACTIVITY_ONRESUME; + ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE; + ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED; + ANDROID_NATIVE_ACTIVITY_ONPAUSE; + ANDROID_NATIVE_ACTIVITY_ONSTOP; + ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED; + ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED; + ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED; + ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED; + ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED; + ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY; + ANDROID_NATIVE_ACTIVITY_ONDESTROY; + ANDROID_NATIVE_ACTIVITY_DONE; + ANDROID_NATIVE_ACTIVITY_ONCREATE; + ANDROID_CREATE_THREAD_PIPE_FAILED; + ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS; + WGPU_SWAPCHAIN_CREATE_SURFACE_FAILED; + WGPU_SWAPCHAIN_CREATE_SWAPCHAIN_FAILED; + WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_TEXTURE_FAILED; + WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_VIEW_FAILED; + WGPU_SWAPCHAIN_CREATE_MSAA_TEXTURE_FAILED; + WGPU_SWAPCHAIN_CREATE_MSAA_VIEW_FAILED; + WGPU_REQUEST_DEVICE_STATUS_ERROR; + WGPU_REQUEST_DEVICE_STATUS_UNKNOWN; + WGPU_REQUEST_ADAPTER_STATUS_UNAVAILABLE; + WGPU_REQUEST_ADAPTER_STATUS_ERROR; + WGPU_REQUEST_ADAPTER_STATUS_UNKNOWN; + WGPU_CREATE_INSTANCE_FAILED; + IMAGE_DATA_SIZE_MISMATCH; + DROPPED_FILE_PATH_TOO_LONG; + CLIPBOARD_STRING_TOO_BIG; +} + +sapp_logger :: struct { + func : (a0: *u8, a1: u32, a2: u32, a3: *u8, a4: u32, a5: *u8, a6: *void) #c_call; + user_data : *void; +} + +sapp_desc :: struct { + init_cb : () #c_call; + frame_cb : () #c_call; + cleanup_cb : () #c_call; + event_cb : (a0: *sapp_event) #c_call; + user_data : *void; + init_userdata_cb : (a0: *void) #c_call; + frame_userdata_cb : (a0: *void) #c_call; + cleanup_userdata_cb : (a0: *void) #c_call; + event_userdata_cb : (a0: *sapp_event, a1: *void) #c_call; + width : s32; + height : s32; + sample_count : s32; + swap_interval : s32; + high_dpi : bool; + fullscreen : bool; + alpha : bool; + window_title : *u8; + enable_clipboard : bool; + clipboard_size : s32; + enable_dragndrop : bool; + max_dropped_files : s32; + max_dropped_file_path_length : s32; + icon : sapp_icon_desc; + allocator : sapp_allocator; + logger : sapp_logger; + gl_major_version : s32; + gl_minor_version : s32; + win32_console_utf8 : bool; + win32_console_create : bool; + win32_console_attach : bool; + html5_canvas_selector : *u8; + html5_canvas_resize : bool; + html5_preserve_drawing_buffer : bool; + html5_premultiplied_alpha : bool; + html5_ask_leave_site : bool; + html5_update_document_title : bool; + html5_bubble_mouse_events : bool; + html5_bubble_touch_events : bool; + html5_bubble_wheel_events : bool; + html5_bubble_key_events : bool; + html5_bubble_char_events : bool; + html5_use_emsc_set_main_loop : bool; + html5_emsc_set_main_loop_simulate_infinite_loop : bool; + ios_keyboard_resizes_canvas : bool; +} + +/* + HTML5 specific: request and response structs for + asynchronously loading dropped-file content. +*/ +sapp_html5_fetch_error :: enum u32 { + FETCH_ERROR_NO_ERROR; + FETCH_ERROR_BUFFER_TOO_SMALL; + FETCH_ERROR_OTHER; +} + +sapp_html5_fetch_response :: struct { + succeeded : bool; + error_code : sapp_html5_fetch_error; + file_index : s32; + data : sapp_range; + buffer : sapp_range; + user_data : *void; +} + +sapp_html5_fetch_request :: struct { + dropped_file_index : s32; + callback : (a0: *sapp_html5_fetch_response) #c_call; + buffer : sapp_range; + user_data : *void; +} + +/* + sapp_mouse_cursor + + Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) +*/ +sapp_mouse_cursor :: enum u32 { + DEFAULT :: 0; + ARROW; + IBEAM; + CROSSHAIR; + POINTING_HAND; + RESIZE_EW; + RESIZE_NS; + RESIZE_NWSE; + RESIZE_NESW; + RESIZE_ALL; + NOT_ALLOWED; +} + diff --git a/modules/sokol-jai/sokol/audio/module.jai b/modules/sokol-jai/sokol/audio/module.jai new file mode 100644 index 0000000..ecb7086 --- /dev/null +++ b/modules/sokol-jai/sokol/audio/module.jai @@ -0,0 +1,627 @@ +// machine generated, do not edit + +/* + + sokol_audio.h -- cross-platform audio-streaming API + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_AUDIO_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + + SOKOL_DUMMY_BACKEND - use a dummy backend + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024) + SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on + macOS instead of using embedded CoreAudio declarations + + If sokol_audio.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Link with the following libraries: + + - on macOS: AudioToolbox + - on iOS: AudioToolbox, AVFoundation + - on FreeBSD: asound + - on Linux: asound + - on Android: aaudio + - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib + - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32 + + FEATURE OVERVIEW + ================ + You provide a mono- or stereo-stream of 32-bit float samples, which + Sokol Audio feeds into platform-specific audio backends: + + - Windows: WASAPI + - Linux: ALSA + - FreeBSD: ALSA + - macOS: CoreAudio + - iOS: CoreAudio+AVAudioSession + - emscripten: WebAudio with ScriptProcessorNode + - Android: AAudio + + Sokol Audio will not do any buffer mixing or volume control, if you have + multiple independent input streams of sample data you need to perform the + mixing yourself before forwarding the data to Sokol Audio. + + There are two mutually exclusive ways to provide the sample data: + + 1. Callback model: You provide a callback function, which will be called + when Sokol Audio needs new samples. On all platforms except emscripten, + this function is called from a separate thread. + 2. Push model: Your code pushes small blocks of sample data from your + main loop or a thread you created. The pushed data is stored in + a ring buffer where it is pulled by the backend code when + needed. + + The callback model is preferred because it is the most direct way to + feed sample data into the audio backends and also has less moving parts + (there is no ring buffer between your code and the audio backend). + + Sometimes it is not possible to generate the audio stream directly in a + callback function running in a separate thread, for such cases Sokol Audio + provides the push-model as a convenience. + + SOKOL AUDIO, SOLOUD AND MINIAUDIO + ================================= + The WASAPI, ALSA and CoreAudio backend code has been taken from the + SoLoud library (with some modifications, so any bugs in there are most + likely my fault). If you need a more fully-featured audio solution, check + out SoLoud, it's excellent: + + https://github.com/jarikomppa/soloud + + Another alternative which feature-wise is somewhere inbetween SoLoud and + sokol-audio might be MiniAudio: + + https://github.com/mackron/miniaudio + + GLOSSARY + ======== + - stream buffer: + The internal audio data buffer, usually provided by the backend API. The + size of the stream buffer defines the base latency, smaller buffers have + lower latency but may cause audio glitches. Bigger buffers reduce or + eliminate glitches, but have a higher base latency. + + - stream callback: + Optional callback function which is called by Sokol Audio when it + needs new samples. On Windows, macOS/iOS and Linux, this is called in + a separate thread, on WebAudio, this is called per-frame in the + browser thread. + + - channel: + A discrete track of audio data, currently 1-channel (mono) and + 2-channel (stereo) is supported and tested. + + - sample: + The magnitude of an audio signal on one channel at a given time. In + Sokol Audio, samples are 32-bit float numbers in the range -1.0 to + +1.0. + + - frame: + The tightly packed set of samples for all channels at a given time. + For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples. + + - packet: + In Sokol Audio, a small chunk of audio data that is moved from the + main thread to the audio streaming thread in order to decouple the + rate at which the main thread provides new audio data, and the + streaming thread consuming audio data. + + WORKING WITH SOKOL AUDIO + ======================== + First call saudio_setup() with your preferred audio playback options. + In most cases you can stick with the default values, these provide + a good balance between low-latency and glitch-free playback + on all audio backends. + + You should always provide a logging callback to be aware of any + warnings and errors. The easiest way is to use sokol_log.h for this: + + #include "sokol_log.h" + // ... + saudio_setup(&(saudio_desc){ + .logger = { + .func = slog_func, + } + }); + + If you want to use the callback-model, you need to provide a stream + callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb, + otherwise keep both function pointers zero-initialized. + + Use push model and default playback parameters: + + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + Use stream callback model and default playback parameters: + + saudio_setup(&(saudio_desc){ + .stream_cb = my_stream_callback + .logger.func = slog_func, + }); + + The standard stream callback doesn't have a user data argument, if you want + that, use the alternative stream_userdata_cb and also set the user_data pointer: + + saudio_setup(&(saudio_desc){ + .stream_userdata_cb = my_stream_callback, + .user_data = &my_data + .logger.func = slog_func, + }); + + The following playback parameters can be provided through the + saudio_desc struct: + + General parameters (both for stream-callback and push-model): + + int sample_rate -- the sample rate in Hz, default: 44100 + int num_channels -- number of channels, default: 1 (mono) + int buffer_frames -- number of frames in streaming buffer, default: 2048 + + The stream callback prototype (either with or without userdata): + + void (*stream_cb)(float* buffer, int num_frames, int num_channels) + void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data) + Function pointer to the user-provide stream callback. + + Push-model parameters: + + int packet_frames -- number of frames in a packet, default: 128 + int num_packets -- number of packets in ring buffer, default: 64 + + The sample_rate and num_channels parameters are only hints for the audio + backend, it isn't guaranteed that those are the values used for actual + playback. + + To get the actual parameters, call the following functions after + saudio_setup(): + + int saudio_sample_rate(void) + int saudio_channels(void); + + It's unlikely that the number of channels will be different than requested, + but a different sample rate isn't uncommon. + + (NOTE: there's an yet unsolved issue when an audio backend might switch + to a different sample rate when switching output devices, for instance + plugging in a bluetooth headset, this case is currently not handled in + Sokol Audio). + + You can check if audio initialization was successful with + saudio_isvalid(). If backend initialization failed for some reason + (for instance when there's no audio device in the machine), this + will return false. Not checking for success won't do any harm, all + Sokol Audio function will silently fail when called after initialization + has failed, so apart from missing audio output, nothing bad will happen. + + Before your application exits, you should call + + saudio_shutdown(); + + This stops the audio thread (on Linux, Windows and macOS/iOS) and + properly shuts down the audio backend. + + THE STREAM CALLBACK MODEL + ========================= + To use Sokol Audio in stream-callback-mode, provide a callback function + like this in the saudio_desc struct when calling saudio_setup(): + + void stream_cb(float* buffer, int num_frames, int num_channels) { + ... + } + + Or the alternative version with a user-data argument: + + void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) { + my_data_t* my_data = (my_data_t*) user_data; + ... + } + + The job of the callback function is to fill the *buffer* with 32-bit + float sample values. + + To output silence, fill the buffer with zeros: + + void stream_cb(float* buffer, int num_frames, int num_channels) { + const int num_samples = num_frames * num_channels; + for (int i = 0; i < num_samples; i++) { + buffer[i] = 0.0f; + } + } + + For stereo output (num_channels == 2), the samples for the left + and right channel are interleaved: + + void stream_cb(float* buffer, int num_frames, int num_channels) { + assert(2 == num_channels); + for (int i = 0; i < num_frames; i++) { + buffer[2*i + 0] = ...; // left channel + buffer[2*i + 1] = ...; // right channel + } + } + + Please keep in mind that the stream callback function is running in a + separate thread, if you need to share data with the main thread you need + to take care yourself to make the access to the shared data thread-safe! + + THE PUSH MODEL + ============== + To use the push-model for providing audio data, simply don't set (keep + zero-initialized) the stream_cb field in the saudio_desc struct when + calling saudio_setup(). + + To provide sample data with the push model, call the saudio_push() + function at regular intervals (for instance once per frame). You can + call the saudio_expect() function to ask Sokol Audio how much room is + in the ring buffer, but if you provide a continuous stream of data + at the right sample rate, saudio_expect() isn't required (it's a simple + way to sync/throttle your sample generation code with the playback + rate though). + + With saudio_push() you may need to maintain your own intermediate sample + buffer, since pushing individual sample values isn't very efficient. + The following example is from the MOD player sample in + sokol-samples (https://github.com/floooh/sokol-samples): + + const int num_frames = saudio_expect(); + if (num_frames > 0) { + const int num_samples = num_frames * saudio_channels(); + read_samples(flt_buf, num_samples); + saudio_push(flt_buf, num_frames); + } + + Another option is to ignore saudio_expect(), and just push samples as they + are generated in small batches. In this case you *need* to generate the + samples at the right sample rate: + + The following example is taken from the Tiny Emulators project + (https://github.com/floooh/chips-test), this is for mono playback, + so (num_samples == num_frames): + + // tick the sound generator + if (ay38910_tick(&sys->psg)) { + // new sample is ready + sys->sample_buffer[sys->sample_pos++] = sys->psg.sample; + if (sys->sample_pos == sys->num_samples) { + // new sample packet is ready + saudio_push(sys->sample_buffer, sys->num_samples); + sys->sample_pos = 0; + } + } + + THE WEBAUDIO BACKEND + ==================== + The WebAudio backend is currently using a ScriptProcessorNode callback to + feed the sample data into WebAudio. ScriptProcessorNode has been + deprecated for a while because it is running from the main thread, with + the default initialization parameters it works 'pretty well' though. + Ultimately Sokol Audio will use Audio Worklets, but this requires a few + more things to fall into place (Audio Worklets implemented everywhere, + SharedArrayBuffers enabled again, and I need to figure out a 'low-cost' + solution in terms of implementation effort, since Audio Worklets are + a lot more complex than ScriptProcessorNode if the audio data needs to come + from the main thread). + + The WebAudio backend is automatically selected when compiling for + emscripten (__EMSCRIPTEN__ define exists). + + https://developers.google.com/web/updates/2017/12/audio-worklet + https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern + + "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/ + + Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/ + + THE COREAUDIO BACKEND + ===================== + The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined). + Since the CoreAudio API is implemented in C (not Objective-C) on macOS the + implementation part of Sokol Audio can be included into a C source file. + + However on iOS, Sokol Audio must be compiled as Objective-C due to it's + reliance on the AVAudioSession object. The iOS code path support both + being compiled with or without ARC (Automatic Reference Counting). + + For thread synchronisation, the CoreAudio backend will use the + pthread_mutex_* functions. + + The incoming floating point samples will be directly forwarded to + CoreAudio without further conversion. + + macOS and iOS applications that use Sokol Audio need to link with + the AudioToolbox framework. + + THE WASAPI BACKEND + ================== + The WASAPI backend is automatically selected when compiling on Windows + (_WIN32 is defined). + + For thread synchronisation a Win32 critical section is used. + + WASAPI may use a different size for its own streaming buffer then requested, + so the base latency may be slightly bigger. The current backend implementation + converts the incoming floating point sample values to signed 16-bit + integers. + + The required Windows system DLLs are linked with #pragma comment(lib, ...), + so you shouldn't need to add additional linker libs in the build process + (otherwise this is a bug which should be fixed in sokol_audio.h). + + THE ALSA BACKEND + ================ + The ALSA backend is automatically selected when compiling on Linux + ('linux' is defined). + + For thread synchronisation, the pthread_mutex_* functions are used. + + Samples are directly forwarded to ALSA in 32-bit float format, no + further conversion is taking place. + + You need to link with the 'asound' library, and the + header must be present (usually both are installed with some sort + of ALSA development package). + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + saudio_setup(&(saudio_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_audio.h + itself though, not any allocations in OS libraries. + + Memory allocation will only happen on the same thread where saudio_setup() + was called, so you don't need to worry about thread-safety. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'saudio' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAUDIO_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_audio.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-audio like this: + + saudio_setup(&(saudio_desc){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + #system_library,link_always "ole32"; + #if DEBUG { sokol_audio_clib :: #library "sokol_audio_windows_x64_gl_debug"; } + else { sokol_audio_clib :: #library "sokol_audio_windows_x64_gl_release"; } + } else { + #system_library,link_always "ole32"; + #if DEBUG { sokol_audio_clib :: #library "sokol_audio_windows_x64_d3d11_debug"; } + else { sokol_audio_clib :: #library "sokol_audio_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + #system_library,link_always "ole32"; + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_windows_x64_gl_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_windows_x64_gl_release"; } + } else { + #system_library,link_always "ole32"; + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_windows_x64_d3d11_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_audio_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + #system_library,link_always "AudioToolbox"; + #if CPU == .ARM64 { + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_arm64_gl_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_x64_gl_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_x64_gl_release"; } + } + } else { + #system_library,link_always "AudioToolbox"; + #if CPU == .ARM64 { + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_arm64_metal_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_x64_metal_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #system_library,link_always "asound"; #system_library,link_always "dl"; #system_library,link_always "pthread"; + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_linux_x64_gl_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_audio_clib :: #library,no_dll "sokol_audio_wasm_gl_debug"; } + else { sokol_audio_clib :: #library,no_dll "sokol_audio_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// setup sokol-audio +saudio_setup :: (desc: *saudio_desc) -> void #foreign sokol_audio_clib; +// shutdown sokol-audio +saudio_shutdown :: () -> void #foreign sokol_audio_clib; +// true after setup if audio backend was successfully initialized +saudio_isvalid :: () -> bool #foreign sokol_audio_clib; +// return the saudio_desc.user_data pointer +saudio_userdata :: () -> *void #foreign sokol_audio_clib; +// return a copy of the original saudio_desc struct +saudio_query_desc :: () -> saudio_desc #foreign sokol_audio_clib; +// actual sample rate +saudio_sample_rate :: () -> s32 #foreign sokol_audio_clib; +// return actual backend buffer size in number of frames +saudio_buffer_frames :: () -> s32 #foreign sokol_audio_clib; +// actual number of channels +saudio_channels :: () -> s32 #foreign sokol_audio_clib; +// return true if audio context is currently suspended (only in WebAudio backend, all other backends return false) +saudio_suspended :: () -> bool #foreign sokol_audio_clib; +// get current number of frames to fill packet queue +saudio_expect :: () -> s32 #foreign sokol_audio_clib; +// push sample frames from main thread, returns number of frames actually pushed +saudio_push :: (frames: *float, num_frames: s32) -> s32 #foreign sokol_audio_clib; + +saudio_log_item :: enum u32 { + OK; + MALLOC_FAILED; + ALSA_SND_PCM_OPEN_FAILED; + ALSA_FLOAT_SAMPLES_NOT_SUPPORTED; + ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED; + ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED; + ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED; + ALSA_SND_PCM_HW_PARAMS_FAILED; + ALSA_PTHREAD_CREATE_FAILED; + WASAPI_CREATE_EVENT_FAILED; + WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED; + WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED; + WASAPI_DEVICE_ACTIVATE_FAILED; + WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED; + WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED; + WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED; + WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED; + WASAPI_CREATE_THREAD_FAILED; + AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED; + AAUDIO_PTHREAD_CREATE_FAILED; + AAUDIO_RESTARTING_STREAM_AFTER_ERROR; + USING_AAUDIO_BACKEND; + AAUDIO_CREATE_STREAMBUILDER_FAILED; + COREAUDIO_NEW_OUTPUT_FAILED; + COREAUDIO_ALLOCATE_BUFFER_FAILED; + COREAUDIO_START_FAILED; + BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE; +} + +saudio_logger :: struct { + func : (a0: *u8, a1: u32, a2: u32, a3: *u8, a4: u32, a5: *u8, a6: *void) #c_call; + user_data : *void; +} + +saudio_allocator :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +saudio_desc :: struct { + sample_rate : s32; + num_channels : s32; + buffer_frames : s32; + packet_frames : s32; + num_packets : s32; + stream_cb : (a0: *float, a1: s32, a2: s32) #c_call; + stream_userdata_cb : (a0: *float, a1: s32, a2: s32, a3: *void) #c_call; + user_data : *void; + allocator : saudio_allocator; + logger : saudio_logger; +} + diff --git a/modules/sokol-jai/sokol/build_clibs_linux.sh b/modules/sokol-jai/sokol/build_clibs_linux.sh new file mode 100755 index 0000000..19decc1 --- /dev/null +++ b/modules/sokol-jai/sokol/build_clibs_linux.sh @@ -0,0 +1,47 @@ +set -e + +build_lib_x64_release() { + src=$1 + dst=$2 + backend=$3 + echo $dst + cc -pthread -c -O2 -DNDEBUG -DIMPL -D$backend c/$src.c + ar rcs $dst.a $src.o +} + +build_lib_x64_debug() { + src=$1 + dst=$2 + backend=$3 + echo $dst + cc -pthread -c -g -DIMPL -D$backend c/$src.c + ar rcs $dst.a $src.o +} + +# x64 + GL + Release +build_lib_x64_release sokol_log log/sokol_log_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_gfx gfx/sokol_gfx_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_app app/sokol_app_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_glue glue/sokol_glue_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_time time/sokol_time_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_audio audio/sokol_audio_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_debugtext debugtext/sokol_debugtext_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_shape shape/sokol_shape_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_gl gl/sokol_gl_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_fontstash fontstash/sokol_fontstash_linux_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_fetch fetch/sokol_fetch_linux_x64_gl_release SOKOL_GLCORE + +# x64 + GL + Debug +build_lib_x64_debug sokol_log log/sokol_log_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_gfx gfx/sokol_gfx_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_app app/sokol_app_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_glue glue/sokol_glue_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_time time/sokol_time_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_audio audio/sokol_audio_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_debugtext debugtext/sokol_debugtext_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_shape shape/sokol_shape_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_fontstash fontstash/sokol_fontstash_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_fetch fetch/sokol_fetch_linux_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_gl gl/sokol_gl_linux_x64_gl_debug SOKOL_GLCORE + +rm *.o diff --git a/modules/sokol-jai/sokol/build_clibs_macos.sh b/modules/sokol-jai/sokol/build_clibs_macos.sh new file mode 100755 index 0000000..4e0ddb6 --- /dev/null +++ b/modules/sokol-jai/sokol/build_clibs_macos.sh @@ -0,0 +1,140 @@ +set -e + +architecture=$(uname -m) + +build_lib_arm64_release() { + if [[ $architecture == "x86_64" ]]; then + return + fi + src=$1 + dst=$2 + backend=$3 + echo $dst + MACOSX_DEPLOYMENT_TARGET=10.13 cc -c -O2 -x objective-c -arch arm64 -DNDEBUG -DIMPL -D$backend c/$src.c + ar rcs $dst.a $src.o +} + +build_lib_arm64_debug() { + if [[ $architecture == "x86_64" ]]; then + return + fi + src=$1 + dst=$2 + backend=$3 + echo $dst + MACOSX_DEPLOYMENT_TARGET=10.13 cc -c -g -x objective-c -arch arm64 -DIMPL -D$backend c/$src.c + ar rcs $dst.a $src.o +} + +build_lib_x64_release() { + src=$1 + dst=$2 + backend=$3 + echo $dst + MACOSX_DEPLOYMENT_TARGET=10.13 cc -c -O2 -x objective-c -arch x86_64 -DNDEBUG -DIMPL -D$backend c/$src.c + ar rcs $dst.a $src.o +} + +build_lib_x64_debug() { + src=$1 + dst=$2 + backend=$3 + echo $dst + MACOSX_DEPLOYMENT_TARGET=10.13 cc -c -g -x objective-c -arch x86_64 -DIMPL -D$backend c/$src.c + ar rcs $dst.a $src.o +} + +# ARM + Metal + Release +build_lib_arm64_release sokol_log log/sokol_log_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_gfx gfx/sokol_gfx_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_app app/sokol_app_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_glue glue/sokol_glue_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_time time/sokol_time_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_audio audio/sokol_audio_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_debugtext debugtext/sokol_debugtext_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_shape shape/sokol_shape_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_gl gl/sokol_gl_macos_arm64_metal_release SOKOL_METAL +build_lib_arm64_release sokol_fontstash fontstash/sokol_fontstash_macos_arm64_metal_release SOKOL_METAL + +# ARM + Metal + Debug +build_lib_arm64_debug sokol_log log/sokol_log_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_gfx gfx/sokol_gfx_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_app app/sokol_app_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_glue glue/sokol_glue_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_time time/sokol_time_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_audio audio/sokol_audio_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_debugtext debugtext/sokol_debugtext_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_shape shape/sokol_shape_macos_arm64_metal_debug SOKOL_METAL +build_lib_arm64_debug sokol_gl gl/sokol_gl_macos_arm64_metal_debug SOKOL_METAL + +# x64 + Metal + Release +build_lib_x64_release sokol_log log/sokol_log_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_gfx gfx/sokol_gfx_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_app app/sokol_app_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_glue glue/sokol_glue_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_time time/sokol_time_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_audio audio/sokol_audio_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_debugtext debugtext/sokol_debugtext_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_shape shape/sokol_shape_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_gl gl/sokol_gl_macos_x64_metal_release SOKOL_METAL +build_lib_x64_release sokol_fontstash fontstash/sokol_fontstash_macos_x64_metal_release SOKOL_METAL + +# x64 + Metal + Debug +build_lib_x64_debug sokol_log log/sokol_log_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_gfx gfx/sokol_gfx_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_app app/sokol_app_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_glue glue/sokol_glue_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_time time/sokol_time_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_audio audio/sokol_audio_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_debugtext debugtext/sokol_debugtext_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_shape shape/sokol_shape_macos_x64_metal_debug SOKOL_METAL +build_lib_x64_debug sokol_gl gl/sokol_gl_macos_x64_metal_debug SOKOL_METAL + +# ARM + GL + Release +build_lib_arm64_release sokol_log log/sokol_log_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_gfx gfx/sokol_gfx_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_app app/sokol_app_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_glue glue/sokol_glue_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_time time/sokol_time_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_audio audio/sokol_audio_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_debugtext debugtext/sokol_debugtext_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_shape shape/sokol_shape_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_gl gl/sokol_gl_macos_arm64_gl_release SOKOL_GLCORE +build_lib_arm64_release sokol_fontstash fontstash/sokol_fontstash_macos_arm64_gl_release SOKOL_GLCORE + +# ARM + GL + Debug +build_lib_arm64_debug sokol_log log/sokol_log_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_gfx gfx/sokol_gfx_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_app app/sokol_app_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_glue glue/sokol_glue_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_time time/sokol_time_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_audio audio/sokol_audio_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_debugtext debugtext/sokol_debugtext_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_shape shape/sokol_shape_macos_arm64_gl_debug SOKOL_GLCORE +build_lib_arm64_debug sokol_gl gl/sokol_gl_macos_arm64_gl_debug SOKOL_GLCORE + +# x64 + GL + Release +build_lib_x64_release sokol_log log/sokol_log_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_gfx gfx/sokol_gfx_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_app app/sokol_app_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_glue glue/sokol_glue_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_time time/sokol_time_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_audio audio/sokol_audio_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_debugtext debugtext/sokol_debugtext_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_shape shape/sokol_shape_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_gl gl/sokol_gl_macos_x64_gl_release SOKOL_GLCORE +build_lib_x64_release sokol_fontstash fontstash/sokol_fontstash_macos_x64_gl_release SOKOL_GLCORE + +# x64 + GL + Debug +build_lib_x64_debug sokol_log log/sokol_log_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_gfx gfx/sokol_gfx_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_app app/sokol_app_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_glue glue/sokol_glue_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_time time/sokol_time_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_audio audio/sokol_audio_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_debugtext debugtext/sokol_debugtext_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_shape shape/sokol_shape_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_gl gl/sokol_gl_macos_x64_gl_debug SOKOL_GLCORE +build_lib_x64_debug sokol_fontstash fontstash/sokol_fontstash_macos_x64_gl_debug SOKOL_GLCORE + +rm *.o diff --git a/modules/sokol-jai/sokol/build_clibs_macos_dylib.sh b/modules/sokol-jai/sokol/build_clibs_macos_dylib.sh new file mode 100755 index 0000000..6e0abc2 --- /dev/null +++ b/modules/sokol-jai/sokol/build_clibs_macos_dylib.sh @@ -0,0 +1,50 @@ +set -e + +FRAMEWORKS_METAL="-framework Metal -framework MetalKit" +FRAMEWORKS_OPENGL="-framework OpenGL" +FRAMEWORKS_CORE="-framework Foundation -framework CoreGraphics -framework Cocoa -framework QuartzCore -framework CoreAudio -framework AudioToolbox" + +build_lib_release() { + src=$1 + dst=$2 + backend=$3 + arch=$4 + frameworks="" + if [ $backend = "SOKOL_METAL" ]; then + frameworks="${frameworks} ${FRAMEWORKS_METAL}" + else + frameworks="${frameworks} ${FRAMEWORKS_OPENGL}" + fi + echo $dst + MACOSX_DEPLOYMENT_TARGET=10.13 cc -c -O2 -x objective-c -arch $arch -DNDEBUG -DIMPL -D$backend c/$src.c + cc -dynamiclib -arch $arch $FRAMEWORKS_CORE $frameworks -o $dst.dylib $src.o $dep +} + +build_lib_debug() { + src=$1 + dst=$2 + backend=$3 + arch=$4 + frameworks="" + if [ $backend = "SOKOL_METAL" ]; then + frameworks="${frameworks} ${FRAMEWORKS_METAL}" + else + frameworks="${frameworks} ${FRAMEWORKS_OPENGL}" + fi + echo $dst + MACOSX_DEPLOYMENT_TARGET=10.13 cc -c -g -x objective-c -arch $arch -DIMPL -D$backend c/$src.c + cc -dynamiclib -arch $arch $FRAMEWORKS_CORE $frameworks -o $dst.dylib $src.o $dep +} + +mkdir -p dylib + +build_lib_release sokol dylib/sokol_dylib_macos_arm64_metal_release SOKOL_METAL arm64 +build_lib_debug sokol dylib/sokol_dylib_macos_arm64_metal_debug SOKOL_METAL arm64 +build_lib_release sokol dylib/sokol_dylib_macos_x64_metal_release SOKOL_METAL x86_64 +build_lib_debug sokol dylib/sokol_dylib_macos_x64_metal_debug SOKOL_METAL x86_64 +build_lib_release sokol dylib/sokol_dylib_macos_arm64_gl_release SOKOL_GLCORE33 arm64 +build_lib_debug sokol dylib/sokol_dylib_macos_arm64_gl_debug SOKOL_GLCORE33 arm64 +build_lib_release sokol dylib/sokol_dylib_macos_x64_gl_release SOKOL_GLCORE33 x86_64 +build_lib_debug sokol dylib/sokol_dylib_macos_x64_gl_debug SOKOL_GLCORE33 x86_64 + +rm *.o diff --git a/modules/sokol-jai/sokol/build_clibs_wasm.sh b/modules/sokol-jai/sokol/build_clibs_wasm.sh new file mode 100755 index 0000000..35f92ca --- /dev/null +++ b/modules/sokol-jai/sokol/build_clibs_wasm.sh @@ -0,0 +1,47 @@ +set -e + +build_lib_wasm_release() { + src=$1 + dst=$2 + backend=$3 + echo $dst + emcc -c -O2 -DNDEBUG -sMEMORY64 -sSHARED_MEMORY -DIMPL -D$backend c/$src.c + emar rcs $dst.a $src.o +} + +build_lib_wasm_debug() { + src=$1 + dst=$2 + backend=$3 + echo $dst + emcc -c -g -DIMPL -sMEMORY64 -sSHARED_MEMORY -D$backend c/$src.c + emar rcs $dst.a $src.o +} + +# wasm + GL + Release +build_lib_wasm_release sokol_log log/sokol_log_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_gfx gfx/sokol_gfx_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_app app/sokol_app_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_glue glue/sokol_glue_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_time time/sokol_time_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_audio audio/sokol_audio_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_debugtext debugtext/sokol_debugtext_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_shape shape/sokol_shape_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_fontstash fontstash/sokol_fontstash_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_fetch fetch/sokol_fetch_wasm_gl_release SOKOL_GLES3 +build_lib_wasm_release sokol_gl gl/sokol_gl_wasm_gl_release SOKOL_GLES3 + +# wasm + GL + Debug +build_lib_wasm_debug sokol_log log/sokol_log_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_gfx gfx/sokol_gfx_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_app app/sokol_app_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_glue glue/sokol_glue_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_time time/sokol_time_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_audio audio/sokol_audio_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_debugtext debugtext/sokol_debugtext_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_shape shape/sokol_shape_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_fontstash fontstash/sokol_fontstash_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_fetch fetch/sokol_fetch_wasm_gl_debug SOKOL_GLES3 +build_lib_wasm_debug sokol_gl gl/sokol_gl_wasm_gl_debug SOKOL_GLES3 + +rm *.o diff --git a/modules/sokol-jai/sokol/build_clibs_windows.cmd b/modules/sokol-jai/sokol/build_clibs_windows.cmd new file mode 100644 index 0000000..f81e79e --- /dev/null +++ b/modules/sokol-jai/sokol/build_clibs_windows.cmd @@ -0,0 +1,45 @@ +@echo off + +set sources=log app gfx glue time audio debugtext shape gl + +REM D3D11 Debug +for %%s in (%sources%) do ( + cl /c /D_DEBUG /DIMPL /DSOKOL_D3D11 c\sokol_%%s.c /Z7 + lib /OUT:%%s\sokol_%%s_windows_x64_d3d11_debug.lib sokol_%%s.obj + del sokol_%%s.obj +) + +REM D3D11 Release +for %%s in (%sources%) do ( + cl /c /O2 /DNDEBUG /DIMPL /DSOKOL_D3D11 c\sokol_%%s.c + lib /OUT:%%s\sokol_%%s_windows_x64_d3d11_release.lib sokol_%%s.obj + del sokol_%%s.obj +) + +REM GL Debug +for %%s in (%sources%) do ( + cl /c /D_DEBUG /DIMPL /DSOKOL_GLCORE c\sokol_%%s.c /Z7 + lib /OUT:%%s\sokol_%%s_windows_x64_gl_debug.lib sokol_%%s.obj + del sokol_%%s.obj +) + +REM GL Release +for %%s in (%sources%) do ( + cl /c /O2 /DNDEBUG /DIMPL /DSOKOL_GLCORE c\sokol_%%s.c + lib /OUT:%%s\sokol_%%s_windows_x64_gl_release.lib sokol_%%s.obj + del sokol_%%s.obj +) + +REM D3D11 Debug DLL +cl /D_DEBUG /DIMPL /DSOKOL_DLL /DSOKOL_D3D11 c\sokol.c /Z7 /LDd /MDd /DLL /Fe:sokol_dll_windows_x64_d3d11_debug.dll /link /INCREMENTAL:NO + +REM D3D11 Release DLL +cl /D_DEBUG /DIMPL /DSOKOL_DLL /DSOKOL_D3D11 c\sokol.c /LD /MD /DLL /Fe:sokol_dll_windows_x64_d3d11_release.dll /link /INCREMENTAL:NO + +REM GL Debug DLL +cl /D_DEBUG /DIMPL /DSOKOL_DLL /DSOKOL_GLCORE c\sokol.c /Z7 /LDd /MDd /DLL /Fe:sokol_dll_windows_x64_gl_debug.dll /link /INCREMENTAL:NO + +REM GL Release DLL +cl /D_DEBUG /DIMPL /DSOKOL_DLL /DSOKOL_GLCORE c\sokol.c /LD /MD /DLL /Fe:sokol_dll_windows_x64_gl_release.dll /link /INCREMENTAL:NO + +del sokol.obj \ No newline at end of file diff --git a/modules/sokol-jai/sokol/c/fontstash.h b/modules/sokol-jai/sokol/c/fontstash.h new file mode 100644 index 0000000..a09e6f1 --- /dev/null +++ b/modules/sokol-jai/sokol/c/fontstash.h @@ -0,0 +1,1714 @@ +// +// NOTE sokol: all IO functions have been removed +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef FONS_H +#define FONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// To make the implementation private to the file that generates the implementation +#ifdef FONS_STATIC +#define FONS_DEF static +#else +#define FONS_DEF extern +#endif + +#define FONS_INVALID -1 + +enum FONSflags { + FONS_ZERO_TOPLEFT = 1, + FONS_ZERO_BOTTOMLEFT = 2, +}; + +enum FONSalign { + // Horizontal align + FONS_ALIGN_LEFT = 1<<0, // Default + FONS_ALIGN_CENTER = 1<<1, + FONS_ALIGN_RIGHT = 1<<2, + // Vertical align + FONS_ALIGN_TOP = 1<<3, + FONS_ALIGN_MIDDLE = 1<<4, + FONS_ALIGN_BOTTOM = 1<<5, + FONS_ALIGN_BASELINE = 1<<6, // Default +}; + +enum FONSerrorCode { + // Font atlas is full. + FONS_ATLAS_FULL = 1, + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + FONS_SCRATCH_FULL = 2, + // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. + FONS_STATES_OVERFLOW = 3, + // Trying to pop too many states fonsPopState(). + FONS_STATES_UNDERFLOW = 4, +}; + +struct FONSparams { + int width, height; + unsigned char flags; + void* userPtr; + int (*renderCreate)(void* uptr, int width, int height); + int (*renderResize)(void* uptr, int width, int height); + void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data); + void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts); + void (*renderDelete)(void* uptr); +}; +typedef struct FONSparams FONSparams; + +struct FONSquad +{ + float x0,y0,s0,t0; + float x1,y1,s1,t1; +}; +typedef struct FONSquad FONSquad; + +struct FONStextIter { + float x, y, nextx, nexty, scale, spacing; + unsigned int codepoint; + short isize, iblur; + struct FONSfont* font; + int prevGlyphIndex; + const char* str; + const char* next; + const char* end; + unsigned int utf8state; +}; +typedef struct FONStextIter FONStextIter; + +typedef struct FONScontext FONScontext; + +// Contructor and destructor. +FONS_DEF FONScontext* fonsCreateInternal(FONSparams* params); +FONS_DEF void fonsDeleteInternal(FONScontext* s); + +FONS_DEF void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); +// Returns current atlas size. +FONS_DEF void fonsGetAtlasSize(FONScontext* s, int* width, int* height); +// Expands the atlas size. +FONS_DEF int fonsExpandAtlas(FONScontext* s, int width, int height); +// Resets the whole stash. +FONS_DEF int fonsResetAtlas(FONScontext* stash, int width, int height); + +// Add fonts +FONS_DEF int fonsGetFontByName(FONScontext* s, const char* name); +FONS_DEF int fonsAddFallbackFont(FONScontext* stash, int base, int fallback); + +// State handling +FONS_DEF void fonsPushState(FONScontext* s); +FONS_DEF void fonsPopState(FONScontext* s); +FONS_DEF void fonsClearState(FONScontext* s); + +// State setting +FONS_DEF void fonsSetSize(FONScontext* s, float size); +FONS_DEF void fonsSetColor(FONScontext* s, unsigned int color); +FONS_DEF void fonsSetSpacing(FONScontext* s, float spacing); +FONS_DEF void fonsSetBlur(FONScontext* s, float blur); +FONS_DEF void fonsSetAlign(FONScontext* s, int align); +FONS_DEF void fonsSetFont(FONScontext* s, int font); + +// Draw text +FONS_DEF float fonsDrawText(FONScontext* s, float x, float y, const char* string, const char* end); + +// Measure text +FONS_DEF float fonsTextBounds(FONScontext* s, float x, float y, const char* string, const char* end, float* bounds); +FONS_DEF void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy); +FONS_DEF void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh); + +// Text iterator +FONS_DEF int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end); +FONS_DEF int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad); + +// Pull texture changes +FONS_DEF const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height); +FONS_DEF int fonsValidateTexture(FONScontext* s, int* dirty); + +// Draws the stash texture for debugging +FONS_DEF void fonsDrawDebug(FONScontext* s, float x, float y); + +#ifdef __cplusplus +} +#endif + +#endif // FONS_H + + +#ifdef FONTSTASH_IMPLEMENTATION + +#define FONS_NOTUSED(v) (void)sizeof(v) + +#ifdef FONS_USE_FREETYPE + +#include +#include FT_FREETYPE_H +#include FT_ADVANCES_H +#include + +struct FONSttFontImpl { + FT_Face font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static FT_Library ftLibrary; + +static int fons__tt_init() +{ + FT_Error ftError; + FONS_NOTUSED(context); + ftError = FT_Init_FreeType(&ftLibrary); + return ftError == 0; +} + +static int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + FT_Error ftError; + FONS_NOTUSED(context); + + //font->font.userdata = stash; + ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font); + return ftError == 0; +} + +static void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + *ascent = font->font->ascender; + *descent = font->font->descender; + *lineGap = font->font->height - (*ascent - *descent); +} + +static float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return size / (font->font->ascender - font->font->descender); +} + +static int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return FT_Get_Char_Index(font->font, codepoint); +} + +static int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FT_Error ftError; + FT_GlyphSlot ftGlyph; + FT_Fixed advFixed; + FONS_NOTUSED(scale); + + ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); + if (ftError) return 0; + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); + if (ftError) return 0; + ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed); + if (ftError) return 0; + ftGlyph = font->font->glyph; + *advance = (int)advFixed; + *lsb = (int)ftGlyph->metrics.horiBearingX; + *x0 = ftGlyph->bitmap_left; + *x1 = *x0 + ftGlyph->bitmap.width; + *y0 = -ftGlyph->bitmap_top; + *y1 = *y0 + ftGlyph->bitmap.rows; + return 1; +} + +static void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + FT_GlyphSlot ftGlyph = font->font->glyph; + int ftGlyphOffset = 0; + int x, y; + FONS_NOTUSED(outWidth); + FONS_NOTUSED(outHeight); + FONS_NOTUSED(scaleX); + FONS_NOTUSED(scaleY); + FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap + + for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { + for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { + output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; + } + } +} + +static int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + FT_Vector ftKerning; + FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); + return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer +} + +#else + +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +static void* fons__tmpalloc(size_t size, void* up); +static void fons__tmpfree(void* ptr, void* up); +#define STBTT_malloc(x,u) fons__tmpalloc(x,u) +#define STBTT_free(x,u) fons__tmpfree(x,u) +#include "stb_truetype.h" + +struct FONSttFontImpl { + stbtt_fontinfo font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static int fons__tt_init(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +static int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + int stbError; + FONS_NOTUSED(dataSize); + + font->font.userdata = context; + stbError = stbtt_InitFont(&font->font, data, 0); + return stbError; +} + +static void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); +} + +static float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return stbtt_ScaleForPixelHeight(&font->font, size); +} + +static int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return stbtt_FindGlyphIndex(&font->font, codepoint); +} + +static int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FONS_NOTUSED(size); + stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); + stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); + return 1; +} + +static void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); +} + +static int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); +} + +#endif + +#ifndef FONS_SCRATCH_BUF_SIZE +# define FONS_SCRATCH_BUF_SIZE 64000 +#endif +#ifndef FONS_HASH_LUT_SIZE +# define FONS_HASH_LUT_SIZE 256 +#endif +#ifndef FONS_INIT_FONTS +# define FONS_INIT_FONTS 4 +#endif +#ifndef FONS_INIT_GLYPHS +# define FONS_INIT_GLYPHS 256 +#endif +#ifndef FONS_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif +#ifndef FONS_VERTEX_COUNT +# define FONS_VERTEX_COUNT 1024 +#endif +#ifndef FONS_MAX_STATES +# define FONS_MAX_STATES 20 +#endif +#ifndef FONS_MAX_FALLBACKS +# define FONS_MAX_FALLBACKS 20 +#endif + +static unsigned int fons__hashint(unsigned int a) +{ + a += ~(a<<15); + a ^= (a>>10); + a += (a<<3); + a ^= (a>>6); + a += ~(a<<11); + a ^= (a>>16); + return a; +} + +static int fons__mini(int a, int b) +{ + return a < b ? a : b; +} + +static int fons__maxi(int a, int b) +{ + return a > b ? a : b; +} + +struct FONSglyph +{ + unsigned int codepoint; + int index; + int next; + short size, blur; + short x0,y0,x1,y1; + short xadv,xoff,yoff; +}; +typedef struct FONSglyph FONSglyph; + +struct FONSfont +{ + FONSttFontImpl font; + char name[64]; + unsigned char* data; + int dataSize; + unsigned char freeData; + float ascender; + float descender; + float lineh; + FONSglyph* glyphs; + int cglyphs; + int nglyphs; + int lut[FONS_HASH_LUT_SIZE]; + int fallbacks[FONS_MAX_FALLBACKS]; + int nfallbacks; +}; +typedef struct FONSfont FONSfont; + +struct FONSstate +{ + int font; + int align; + float size; + unsigned int color; + float blur; + float spacing; +}; +typedef struct FONSstate FONSstate; + +struct FONSatlasNode { + short x, y, width; +}; +typedef struct FONSatlasNode FONSatlasNode; + +struct FONSatlas +{ + int width, height; + FONSatlasNode* nodes; + int nnodes; + int cnodes; +}; +typedef struct FONSatlas FONSatlas; + +struct FONScontext +{ + FONSparams params; + float itw,ith; + unsigned char* texData; + int dirtyRect[4]; + FONSfont** fonts; + FONSatlas* atlas; + int cfonts; + int nfonts; + float verts[FONS_VERTEX_COUNT*2]; + float tcoords[FONS_VERTEX_COUNT*2]; + unsigned int colors[FONS_VERTEX_COUNT]; + int nverts; + unsigned char* scratch; + int nscratch; + FONSstate states[FONS_MAX_STATES]; + int nstates; + void (*handleError)(void* uptr, int error, int val); + void* errorUptr; +}; + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +static void* fons__tmpalloc(size_t size, void* up) +{ + unsigned char* ptr; + FONScontext* stash = (FONScontext*)up; + + // 16-byte align the returned pointer + size = (size + 0xf) & ~0xf; + + if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size); + return NULL; + } + ptr = stash->scratch + stash->nscratch; + stash->nscratch += (int)size; + return ptr; +} + +static void fons__tmpfree(void* ptr, void* up) +{ + (void)ptr; + (void)up; + // empty +} + +#endif // STB_TRUETYPE_IMPLEMENTATION + +// Copyright (c) 2008-2010 Bjoern Hoehrmann +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define FONS_UTF8_ACCEPT 0 +#define FONS_UTF8_REJECT 12 + +static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte) +{ + static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + }; + + unsigned int type = utf8d[byte]; + + *codep = (*state != FONS_UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +// Atlas based on Skyline Bin Packer by Jukka Jylänki + +static void fons__deleteAtlas(FONSatlas* atlas) +{ + if (atlas == NULL) return; + if (atlas->nodes != NULL) free(atlas->nodes); + free(atlas); +} + +static FONSatlas* fons__allocAtlas(int w, int h, int nnodes) +{ + FONSatlas* atlas = NULL; + + // Allocate memory for the font stash. + atlas = (FONSatlas*)malloc(sizeof(FONSatlas)); + if (atlas == NULL) goto error; + memset(atlas, 0, sizeof(FONSatlas)); + + atlas->width = w; + atlas->height = h; + + // Allocate space for skyline nodes + atlas->nodes = (FONSatlasNode*)malloc(sizeof(FONSatlasNode) * nnodes); + if (atlas->nodes == NULL) goto error; + memset(atlas->nodes, 0, sizeof(FONSatlasNode) * nnodes); + atlas->nnodes = 0; + atlas->cnodes = nnodes; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; + + return atlas; + +error: + if (atlas) fons__deleteAtlas(atlas); + return NULL; +} + +static int fons__atlasInsertNode(FONSatlas* atlas, int idx, int x, int y, int w) +{ + int i; + // Insert node + if (atlas->nnodes+1 > atlas->cnodes) { + atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; + atlas->nodes = (FONSatlasNode*)realloc(atlas->nodes, sizeof(FONSatlasNode) * atlas->cnodes); + if (atlas->nodes == NULL) + return 0; + } + for (i = atlas->nnodes; i > idx; i--) + atlas->nodes[i] = atlas->nodes[i-1]; + atlas->nodes[idx].x = (short)x; + atlas->nodes[idx].y = (short)y; + atlas->nodes[idx].width = (short)w; + atlas->nnodes++; + + return 1; +} + +static void fons__atlasRemoveNode(FONSatlas* atlas, int idx) +{ + int i; + if (atlas->nnodes == 0) return; + for (i = idx; i < atlas->nnodes-1; i++) + atlas->nodes[i] = atlas->nodes[i+1]; + atlas->nnodes--; +} + +static void fons__atlasExpand(FONSatlas* atlas, int w, int h) +{ + // Insert node for empty space + if (w > atlas->width) + fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width); + atlas->width = w; + atlas->height = h; +} + +static void fons__atlasReset(FONSatlas* atlas, int w, int h) +{ + atlas->width = w; + atlas->height = h; + atlas->nnodes = 0; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; +} + +static int fons__atlasAddSkylineLevel(FONSatlas* atlas, int idx, int x, int y, int w, int h) +{ + int i; + + // Insert new node + if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) + return 0; + + // Delete skyline segments that fall under the shadow of the new segment. + for (i = idx+1; i < atlas->nnodes; i++) { + if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { + int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; + atlas->nodes[i].x += (short)shrink; + atlas->nodes[i].width -= (short)shrink; + if (atlas->nodes[i].width <= 0) { + fons__atlasRemoveNode(atlas, i); + i--; + } else { + break; + } + } else { + break; + } + } + + // Merge same height skyline segments that are next to each other. + for (i = 0; i < atlas->nnodes-1; i++) { + if (atlas->nodes[i].y == atlas->nodes[i+1].y) { + atlas->nodes[i].width += atlas->nodes[i+1].width; + fons__atlasRemoveNode(atlas, i+1); + i--; + } + } + + return 1; +} + +static int fons__atlasRectFits(FONSatlas* atlas, int i, int w, int h) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + int x = atlas->nodes[i].x; + int y = atlas->nodes[i].y; + int spaceLeft; + if (x + w > atlas->width) + return -1; + spaceLeft = w; + while (spaceLeft > 0) { + if (i == atlas->nnodes) return -1; + y = fons__maxi(y, atlas->nodes[i].y); + if (y + h > atlas->height) return -1; + spaceLeft -= atlas->nodes[i].width; + ++i; + } + return y; +} + +static int fons__atlasAddRect(FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +{ + int besth = atlas->height, bestw = atlas->width, besti = -1; + int bestx = -1, besty = -1, i; + + // Bottom left fit heuristic. + for (i = 0; i < atlas->nnodes; i++) { + int y = fons__atlasRectFits(atlas, i, rw, rh); + if (y != -1) { + if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { + besti = i; + bestw = atlas->nodes[i].width; + besth = y + rh; + bestx = atlas->nodes[i].x; + besty = y; + } + } + } + + if (besti == -1) + return 0; + + // Perform the actual packing. + if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) + return 0; + + *rx = bestx; + *ry = besty; + + return 1; +} + +static void fons__addWhiteRect(FONScontext* stash, int w, int h) +{ + int x, y, gx, gy; + unsigned char* dst; + if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0) + return; + + // Rasterize + dst = &stash->texData[gx + gy * stash->params.width]; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + dst[x] = 0xff; + dst += stash->params.width; + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h); +} + +FONScontext* fonsCreateInternal(FONSparams* params) +{ + FONScontext* stash = NULL; + + // Allocate memory for the font stash. + stash = (FONScontext*)malloc(sizeof(FONScontext)); + if (stash == NULL) goto error; + memset(stash, 0, sizeof(FONScontext)); + + stash->params = *params; + + // Allocate scratch buffer. + stash->scratch = (unsigned char*)malloc(FONS_SCRATCH_BUF_SIZE); + if (stash->scratch == NULL) goto error; + + // Initialize implementation library + if (!fons__tt_init(stash)) goto error; + + if (stash->params.renderCreate != NULL) { + if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) + goto error; + } + + stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); + if (stash->atlas == NULL) goto error; + + // Allocate space for fonts. + stash->fonts = (FONSfont**)malloc(sizeof(FONSfont*) * FONS_INIT_FONTS); + if (stash->fonts == NULL) goto error; + memset(stash->fonts, 0, sizeof(FONSfont*) * FONS_INIT_FONTS); + stash->cfonts = FONS_INIT_FONTS; + stash->nfonts = 0; + + // Create texture for the cache. + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height); + if (stash->texData == NULL) goto error; + memset(stash->texData, 0, stash->params.width * stash->params.height); + + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + fonsPushState(stash); + fonsClearState(stash); + + return stash; + +error: + fonsDeleteInternal(stash); + return NULL; +} + +static FONSstate* fons__getState(FONScontext* stash) +{ + return &stash->states[stash->nstates-1]; +} + +int fonsAddFallbackFont(FONScontext* stash, int base, int fallback) +{ + FONSfont* baseFont = stash->fonts[base]; + if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) { + baseFont->fallbacks[baseFont->nfallbacks++] = fallback; + return 1; + } + return 0; +} + +void fonsSetSize(FONScontext* stash, float size) +{ + fons__getState(stash)->size = size; +} + +void fonsSetColor(FONScontext* stash, unsigned int color) +{ + fons__getState(stash)->color = color; +} + +void fonsSetSpacing(FONScontext* stash, float spacing) +{ + fons__getState(stash)->spacing = spacing; +} + +void fonsSetBlur(FONScontext* stash, float blur) +{ + fons__getState(stash)->blur = blur; +} + +void fonsSetAlign(FONScontext* stash, int align) +{ + fons__getState(stash)->align = align; +} + +void fonsSetFont(FONScontext* stash, int font) +{ + fons__getState(stash)->font = font; +} + +void fonsPushState(FONScontext* stash) +{ + if (stash->nstates >= FONS_MAX_STATES) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0); + return; + } + if (stash->nstates > 0) + memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(FONSstate)); + stash->nstates++; +} + +void fonsPopState(FONScontext* stash) +{ + if (stash->nstates <= 1) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0); + return; + } + stash->nstates--; +} + +void fonsClearState(FONScontext* stash) +{ + FONSstate* state = fons__getState(stash); + state->size = 12.0f; + state->color = 0xffffffff; + state->font = 0; + state->blur = 0; + state->spacing = 0; + state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE; +} + +static void fons__freeFont(FONSfont* font) +{ + if (font == NULL) return; + if (font->glyphs) free(font->glyphs); + if (font->freeData && font->data) free(font->data); + free(font); +} + +static int fons__allocFont(FONScontext* stash) +{ + FONSfont* font = NULL; + if (stash->nfonts+1 > stash->cfonts) { + stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; + stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); + if (stash->fonts == NULL) + return -1; + } + font = (FONSfont*)malloc(sizeof(FONSfont)); + if (font == NULL) goto error; + memset(font, 0, sizeof(FONSfont)); + + font->glyphs = (FONSglyph*)malloc(sizeof(FONSglyph) * FONS_INIT_GLYPHS); + if (font->glyphs == NULL) goto error; + font->cglyphs = FONS_INIT_GLYPHS; + font->nglyphs = 0; + + stash->fonts[stash->nfonts++] = font; + return stash->nfonts-1; + +error: + fons__freeFont(font); + + return FONS_INVALID; +} + +int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData) +{ + int i, ascent, descent, fh, lineGap; + FONSfont* font; + + int idx = fons__allocFont(stash); + if (idx == FONS_INVALID) + return FONS_INVALID; + + font = stash->fonts[idx]; + + strncpy(font->name, name, sizeof(font->name)); + font->name[sizeof(font->name)-1] = '\0'; + + // Init hash lookup. + for (i = 0; i < FONS_HASH_LUT_SIZE; ++i) + font->lut[i] = -1; + + // Read in the font data. + font->dataSize = dataSize; + font->data = data; + font->freeData = (unsigned char)freeData; + + // Init font + stash->nscratch = 0; + if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; + + // Store normalized line height. The real line height is got + // by multiplying the lineh by font size. + fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); + fh = ascent - descent; + font->ascender = (float)ascent / (float)fh; + font->descender = (float)descent / (float)fh; + font->lineh = (float)(fh + lineGap) / (float)fh; + + return idx; + +error: + fons__freeFont(font); + stash->nfonts--; + return FONS_INVALID; +} + +int fonsGetFontByName(FONScontext* s, const char* name) +{ + int i; + for (i = 0; i < s->nfonts; i++) { + if (strcmp(s->fonts[i]->name, name) == 0) + return i; + } + return FONS_INVALID; +} + + +static FONSglyph* fons__allocGlyph(FONSfont* font) +{ + if (font->nglyphs+1 > font->cglyphs) { + font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; + font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); + if (font->glyphs == NULL) return NULL; + } + font->nglyphs++; + return &font->glyphs[font->nglyphs-1]; +} + + +// Based on Exponential blur, Jani Huhtanen, 2006 + +#define APREC 16 +#define ZPREC 7 + +static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (y = 0; y < h; y++) { + int z = 0; // force zero border + for (x = 1; x < w; x++) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[w-1] = 0; // force zero border + z = 0; + for (x = w-2; x >= 0; x--) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst += dstStride; + } +} + +static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (x = 0; x < w; x++) { + int z = 0; // force zero border + for (y = dstStride; y < h*dstStride; y += dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[(h-1)*dstStride] = 0; // force zero border + z = 0; + for (y = (h-2)*dstStride; y >= 0; y -= dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst++; + } +} + + +static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur) +{ + int alpha; + float sigma; + (void)stash; + + if (blur < 1) + return; + // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) + sigma = (float)blur * 0.57735f; // 1 / sqrt(3) + alpha = (int)((1< 20) iblur = 20; + pad = iblur+2; + + // Reset allocator. + stash->nscratch = 0; + + // Find code point and size. + h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); + i = font->lut[h]; + while (i != -1) { + if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) + return &font->glyphs[i]; + i = font->glyphs[i].next; + } + + // Could not find glyph, create it. + g = fons__tt_getGlyphIndex(&font->font, codepoint); + // Try to find the glyph in fallback fonts. + if (g == 0) { + for (i = 0; i < font->nfallbacks; ++i) { + FONSfont* fallbackFont = stash->fonts[font->fallbacks[i]]; + int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont->font, codepoint); + if (fallbackIndex != 0) { + g = fallbackIndex; + renderFont = fallbackFont; + break; + } + } + // It is possible that we did not find a fallback glyph. + // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. + } + scale = fons__tt_getPixelHeightScale(&renderFont->font, size); + fons__tt_buildGlyphBitmap(&renderFont->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); + gw = x1-x0 + pad*2; + gh = y1-y0 + pad*2; + + // Find free spot for the rect in the atlas + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + if (added == 0 && stash->handleError != NULL) { + // Atlas is full, let the user to resize the atlas (or not), and try again. + stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + } + if (added == 0) return NULL; + + // Init glyph. + glyph = fons__allocGlyph(font); + glyph->codepoint = codepoint; + glyph->size = isize; + glyph->blur = iblur; + glyph->index = g; + glyph->x0 = (short)gx; + glyph->y0 = (short)gy; + glyph->x1 = (short)(glyph->x0+gw); + glyph->y1 = (short)(glyph->y0+gh); + glyph->xadv = (short)(scale * advance * 10.0f); + glyph->xoff = (short)(x0 - pad); + glyph->yoff = (short)(y0 - pad); + glyph->next = 0; + + // Insert char to hash lookup. + glyph->next = font->lut[h]; + font->lut[h] = font->nglyphs-1; + + // Rasterize + dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; + fons__tt_renderGlyphBitmap(&renderFont->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g); + + // Make sure there is one pixel empty border. + dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + dst[y*stash->params.width] = 0; + dst[gw-1 + y*stash->params.width] = 0; + } + for (x = 0; x < gw; x++) { + dst[x] = 0; + dst[x + (gh-1)*stash->params.width] = 0; + } + + // Debug code to color the glyph background +/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + for (x = 0; x < gw; x++) { + int a = (int)fdst[x+y*stash->params.width] + 20; + if (a > 255) a = 255; + fdst[x+y*stash->params.width] = a; + } + }*/ + + // Blur + if (iblur > 0) { + stash->nscratch = 0; + bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + fons__blur(stash, bdst, gw,gh, stash->params.width, iblur); + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1); + + return glyph; +} + +static void fons__getQuad(FONScontext* stash, FONSfont* font, + int prevGlyphIndex, FONSglyph* glyph, + float scale, float spacing, float* x, float* y, FONSquad* q) +{ + float rx,ry,xoff,yoff,x0,y0,x1,y1; + + if (prevGlyphIndex != -1) { + float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale; + *x += (int)(adv + spacing + 0.5f); + } + + // Each glyph has 2px border to allow good interpolation, + // one pixel to prevent leaking, and one to allow good interpolation for rendering. + // Inset the texture region by one pixel for correct interpolation. + xoff = (short)(glyph->xoff+1); + yoff = (short)(glyph->yoff+1); + x0 = (float)(glyph->x0+1); + y0 = (float)(glyph->y0+1); + x1 = (float)(glyph->x1-1); + y1 = (float)(glyph->y1-1); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y + yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry + y1 - y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } else { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y - yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry - y1 + y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } + + *x += (int)(glyph->xadv / 10.0f + 0.5f); +} + +static void fons__flush(FONScontext* stash) +{ + // Flush texture + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + if (stash->params.renderUpdate != NULL) + stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData); + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + } + + // Flush triangles + if (stash->nverts > 0) { + if (stash->params.renderDraw != NULL) + stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts); + stash->nverts = 0; + } +} + +static __inline void fons__vertex(FONScontext* stash, float x, float y, float s, float t, unsigned int c) +{ + stash->verts[stash->nverts*2+0] = x; + stash->verts[stash->nverts*2+1] = y; + stash->tcoords[stash->nverts*2+0] = s; + stash->tcoords[stash->nverts*2+1] = t; + stash->colors[stash->nverts] = c; + stash->nverts++; +} + +static float fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize) +{ + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (align & FONS_ALIGN_TOP) { + return font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return font->descender * (float)isize/10.0f; + } + } else { + if (align & FONS_ALIGN_TOP) { + return -font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return -font->descender * (float)isize/10.0f; + } + } + return 0.0; +} + +FONS_DEF float fonsDrawText(FONScontext* stash, + float x, float y, + const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSglyph* glyph = NULL; + FONSquad q; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float width; + + if (stash == NULL) return x; + if (state->font < 0 || state->font >= stash->nfonts) return x; + font = stash->fonts[state->font]; + if (font->data == NULL) return x; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + if (end == NULL) + end = str + strlen(str); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + fons__flush(stash); + + return x; +} + +FONS_DEF int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, + float x, float y, const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + float width; + + memset(iter, 0, sizeof(*iter)); + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + iter->font = stash->fonts[state->font]; + if (iter->font->data == NULL) return 0; + + iter->isize = (short)(state->size*10.0f); + iter->iblur = (short)state->blur; + iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, iter->font, state->align, iter->isize); + + if (end == NULL) + end = str + strlen(str); + + iter->x = iter->nextx = x; + iter->y = iter->nexty = y; + iter->spacing = state->spacing; + iter->str = str; + iter->next = str; + iter->end = end; + iter->codepoint = 0; + iter->prevGlyphIndex = -1; + + return 1; +} + +FONS_DEF int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad) +{ + FONSglyph* glyph = NULL; + const char* str = iter->next; + iter->str = iter->next; + + if (str == iter->end) + return 0; + + for (; str != iter->end; str++) { + if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) + continue; + str++; + // Get glyph and quad + iter->x = iter->nextx; + iter->y = iter->nexty; + glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); + if (glyph != NULL) + fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); + iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1; + break; + } + iter->next = str; + + return 1; +} + +FONS_DEF void fonsDrawDebug(FONScontext* stash, float x, float y) +{ + int i; + int w = stash->params.width; + int h = stash->params.height; + float u = w == 0 ? 0 : (1.0f / w); + float v = h == 0 ? 0 : (1.0f / h); + + if (stash->nverts+6+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + // Draw background + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff); + + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + + // Draw texture + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff); + + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + + // Drawbug draw atlas + for (i = 0; i < stash->atlas->nnodes; i++) { + FONSatlasNode* n = &stash->atlas->nodes[i]; + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + } + + fons__flush(stash); +} + +FONS_DEF float fonsTextBounds(FONScontext* stash, + float x, float y, + const char* str, const char* end, + float* bounds) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSquad q; + FONSglyph* glyph = NULL; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float startx, advance; + float minx, miny, maxx, maxy; + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + font = stash->fonts[state->font]; + if (font->data == NULL) return 0; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + minx = maxx = x; + miny = maxy = y; + startx = x; + + if (end == NULL) + end = str + strlen(str); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + if (q.x0 < minx) minx = q.x0; + if (q.x1 > maxx) maxx = q.x1; + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (q.y0 < miny) miny = q.y0; + if (q.y1 > maxy) maxy = q.y1; + } else { + if (q.y1 < miny) miny = q.y1; + if (q.y0 > maxy) maxy = q.y0; + } + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + + advance = x - startx; + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + minx -= advance; + maxx -= advance; + } else if (state->align & FONS_ALIGN_CENTER) { + minx -= advance * 0.5f; + maxx -= advance * 0.5f; + } + + if (bounds) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } + + return advance; +} + +FONS_DEF void fonsVertMetrics(FONScontext* stash, + float* ascender, float* descender, float* lineh) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + if (ascender) + *ascender = font->ascender*isize/10.0f; + if (descender) + *descender = font->descender*isize/10.0f; + if (lineh) + *lineh = font->lineh*isize/10.0f; +} + +FONS_DEF void fonsLineBounds(FONScontext* stash, float y, float* miny, float* maxy) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + y += fons__getVertAlign(stash, font, state->align, isize); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + *miny = y - font->ascender * (float)isize/10.0f; + *maxy = *miny + font->lineh*isize/10.0f; + } else { + *maxy = y + font->descender * (float)isize/10.0f; + *miny = *maxy - font->lineh*isize/10.0f; + } +} + +FONS_DEF const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height) +{ + if (width != NULL) + *width = stash->params.width; + if (height != NULL) + *height = stash->params.height; + return stash->texData; +} + +FONS_DEF int fonsValidateTexture(FONScontext* stash, int* dirty) +{ + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + dirty[0] = stash->dirtyRect[0]; + dirty[1] = stash->dirtyRect[1]; + dirty[2] = stash->dirtyRect[2]; + dirty[3] = stash->dirtyRect[3]; + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + return 1; + } + return 0; +} + +FONS_DEF void fonsDeleteInternal(FONScontext* stash) +{ + int i; + if (stash == NULL) return; + + if (stash->params.renderDelete) + stash->params.renderDelete(stash->params.userPtr); + + for (i = 0; i < stash->nfonts; ++i) + fons__freeFont(stash->fonts[i]); + + if (stash->atlas) fons__deleteAtlas(stash->atlas); + if (stash->fonts) free(stash->fonts); + if (stash->texData) free(stash->texData); + if (stash->scratch) free(stash->scratch); + free(stash); +} + +FONS_DEF void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) +{ + if (stash == NULL) return; + stash->handleError = callback; + stash->errorUptr = uptr; +} + +FONS_DEF void fonsGetAtlasSize(FONScontext* stash, int* width, int* height) +{ + if (stash == NULL) return; + *width = stash->params.width; + *height = stash->params.height; +} + +FONS_DEF int fonsExpandAtlas(FONScontext* stash, int width, int height) +{ + int i, maxy = 0; + unsigned char* data = NULL; + if (stash == NULL) return 0; + + width = fons__maxi(width, stash->params.width); + height = fons__maxi(height, stash->params.height); + + if (width == stash->params.width && height == stash->params.height) + return 1; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + // Copy old texture data over. + data = (unsigned char*)malloc(width * height); + if (data == NULL) + return 0; + for (i = 0; i < stash->params.height; i++) { + unsigned char* dst = &data[i*width]; + unsigned char* src = &stash->texData[i*stash->params.width]; + memcpy(dst, src, stash->params.width); + if (width > stash->params.width) + memset(dst+stash->params.width, 0, width - stash->params.width); + } + if (height > stash->params.height) + memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width); + + free(stash->texData); + stash->texData = data; + + // Increase atlas size + fons__atlasExpand(stash->atlas, width, height); + + // Add existing data as dirty. + for (i = 0; i < stash->atlas->nnodes; i++) + maxy = fons__maxi(maxy, stash->atlas->nodes[i].y); + stash->dirtyRect[0] = 0; + stash->dirtyRect[1] = 0; + stash->dirtyRect[2] = stash->params.width; + stash->dirtyRect[3] = maxy; + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + return 1; +} + +FONS_DEF int fonsResetAtlas(FONScontext* stash, int width, int height) +{ + int i, j; + if (stash == NULL) return 0; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + + // Reset atlas + fons__atlasReset(stash->atlas, width, height); + + // Clear texture data. + stash->texData = (unsigned char*)realloc(stash->texData, width * height); + if (stash->texData == NULL) return 0; + memset(stash->texData, 0, width * height); + + // Reset dirty rect + stash->dirtyRect[0] = width; + stash->dirtyRect[1] = height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Reset cached glyphs + for (i = 0; i < stash->nfonts; i++) { + FONSfont* font = stash->fonts[i]; + font->nglyphs = 0; + for (j = 0; j < FONS_HASH_LUT_SIZE; j++) + font->lut[j] = -1; + } + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + return 1; +} + +#endif // FONTSTASH_IMPLEMENTATION diff --git a/modules/sokol-jai/sokol/c/sokol.c b/modules/sokol-jai/sokol/c/sokol.c new file mode 100644 index 0000000..2e91988 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol.c @@ -0,0 +1,17 @@ +#if defined(IMPL) +#define SOKOL_IMPL +#endif + +#include "sokol_defines.h" + +#include "sokol_audio.h" +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "sokol_log.h" +#include "sokol_time.h" +#include "sokol_glue.h" +#include "sokol_fetch.h" + +#include "sokol_gl.h" +#include "sokol_shape.h" +#include "sokol_debugtext.h" diff --git a/modules/sokol-jai/sokol/c/sokol_app.c b/modules/sokol-jai/sokol/c/sokol_app.c new file mode 100644 index 0000000..8cf2b17 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_app.c @@ -0,0 +1,5 @@ +#if defined(IMPL) +#define SOKOL_APP_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_app.h" diff --git a/modules/sokol-jai/sokol/c/sokol_app.h b/modules/sokol-jai/sokol/c/sokol_app.h new file mode 100644 index 0000000..c91b68b --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_app.h @@ -0,0 +1,12437 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_APP_IMPL) +#define SOKOL_APP_IMPL +#endif +#ifndef SOKOL_APP_INCLUDED +/* + sokol_app.h -- cross-platform application wrapper + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_APP_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the 3D-API + which should be initialized by sokol_app.h (this must also match + the backend selected for sokol_gfx.h if both are used in the same + project): + + #define SOKOL_GLCORE + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_NOAPI + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_WIN32_FORCE_MAIN - define this on Win32 to add a main() entry point + SOKOL_WIN32_FORCE_WINMAIN - define this on Win32 to add a WinMain() entry point (enabled by default unless SOKOL_WIN32_FORCE_MAIN or SOKOL_NO_ENTRY is defined) + SOKOL_NO_ENTRY - define this if sokol_app.h shouldn't "hijack" the main() function + SOKOL_APP_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_APP_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + If sokol_app.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + if SOKOL_WIN32_FORCE_MAIN and SOKOL_WIN32_FORCE_WINMAIN are both defined, + it is up to the developer to define the desired subsystem. + + On Linux, SOKOL_GLCORE can use either GLX or EGL. + GLX is default, set SOKOL_FORCE_EGL to override. + + For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp + + Portions of the Windows and Linux GL initialization, event-, icon- etc... code + have been taken from GLFW (http://www.glfw.org/). + + iOS onscreen keyboard support 'inspired' by libgdx. + + Link with the following system libraries: + + - on macOS with Metal: Cocoa, QuartzCore, Metal, MetalKit + - on macOS with GL: Cocoa, QuartzCore, OpenGL + - on iOS with Metal: Foundation, UIKit, Metal, MetalKit + - on iOS with GL: Foundation, UIKit, OpenGLES, GLKit + - on Linux with EGL: X11, Xi, Xcursor, EGL, GL (or GLESv2), dl, pthread, m(?) + - on Linux with GLX: X11, Xi, Xcursor, GL, dl, pthread, m(?) + - on Android: GLESv3, EGL, log, android + - on Windows with the MSVC or Clang toolchains: no action needed, libs are defined in-source via pragma-comment-lib + - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' so that _WIN32 is defined + - link with the following libs: -lkernel32 -luser32 -lshell32 + - additionally with the GL backend: -lgdi32 + - additionally with the D3D11 backend: -ld3d11 -ldxgi + + On Linux, you also need to use the -pthread compiler and linker option, otherwise weird + things will happen, see here for details: https://github.com/floooh/sokol/issues/376 + + On macOS and iOS, the implementation must be compiled as Objective-C. + + FEATURE OVERVIEW + ================ + sokol_app.h provides a minimalistic cross-platform API which + implements the 'application-wrapper' parts of a 3D application: + + - a common application entry function + - creates a window and 3D-API context/device with a 'default framebuffer' + - makes the rendered frame visible + - provides keyboard-, mouse- and low-level touch-events + - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android + - 3D-APIs: Metal, D3D11, GL4.1, GL4.3, GLES3, WebGL, WebGL2, NOAPI + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 4.x | YES | YES | YES | --- | --- | --- + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | YES | --- | --- | --- | --- | --- + noapi | YES | TODO | TODO | --- | TODO | --- + KEY_DOWN | YES | YES | YES | SOME | TODO | YES + KEY_UP | YES | YES | YES | SOME | TODO | YES + CHAR | YES | YES | YES | YES | TODO | YES + MOUSE_DOWN | YES | YES | YES | --- | --- | YES + MOUSE_UP | YES | YES | YES | --- | --- | YES + MOUSE_SCROLL | YES | YES | YES | --- | --- | YES + MOUSE_MOVE | YES | YES | YES | --- | --- | YES + MOUSE_ENTER | YES | YES | YES | --- | --- | YES + MOUSE_LEAVE | YES | YES | YES | --- | --- | YES + TOUCHES_BEGAN | --- | --- | --- | YES | YES | YES + TOUCHES_MOVED | --- | --- | --- | YES | YES | YES + TOUCHES_ENDED | --- | --- | --- | YES | YES | YES + TOUCHES_CANCELLED | --- | --- | --- | YES | YES | YES + RESIZED | YES | YES | YES | YES | YES | YES + ICONIFIED | YES | YES | YES | --- | --- | --- + RESTORED | YES | YES | YES | --- | --- | --- + FOCUSED | YES | YES | YES | --- | --- | YES + UNFOCUSED | YES | YES | YES | --- | --- | YES + SUSPENDED | --- | --- | --- | YES | YES | TODO + RESUMED | --- | --- | --- | YES | YES | TODO + QUIT_REQUESTED | YES | YES | YES | --- | --- | YES + IME | TODO | TODO? | TODO | ??? | TODO | ??? + key repeat flag | YES | YES | YES | --- | --- | YES + windowed | YES | YES | YES | --- | --- | YES + fullscreen | YES | YES | YES | YES | YES | --- + mouse hide | YES | YES | YES | --- | --- | YES + mouse lock | YES | YES | YES | --- | --- | YES + set cursor type | YES | YES | YES | --- | --- | YES + screen keyboard | --- | --- | --- | YES | TODO | YES + swap interval | YES | YES | YES | YES | TODO | YES + high-dpi | YES | YES | TODO | YES | YES | YES + clipboard | YES | YES | YES | --- | --- | YES + MSAA | YES | YES | YES | YES | YES | YES + drag'n'drop | YES | YES | YES | --- | --- | YES + window icon | YES | YES(1)| YES | --- | --- | YES + + (1) macOS has no regular window icons, instead the dock icon is changed + (2) supported with EGL only (not GLX) + + STEP BY STEP + ============ + --- Add a sokol_main() function to your code which returns a sapp_desc structure + with initialization parameters and callback function pointers. This + function is called very early, usually at the start of the + platform's entry function (e.g. main or WinMain). You should do as + little as possible here, since the rest of your code might be called + from another thread (this depends on the platform): + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + .width = 640, + .height = 480, + .init_cb = my_init_func, + .frame_cb = my_frame_func, + .cleanup_cb = my_cleanup_func, + .event_cb = my_event_func, + ... + }; + } + + To get any logging output in case of errors you need to provide a log + callback. The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + There are many more setup parameters, but these are the most important. + For a complete list search for the sapp_desc structure declaration + below. + + DO NOT call any sokol-app function from inside sokol_main(), since + sokol-app will not be initialized at this point. + + The .width and .height parameters are the preferred size of the 3D + rendering canvas. The actual size may differ from this depending on + platform and other circumstances. Also the canvas size may change at + any time (for instance when the user resizes the application window, + or rotates the mobile device). You can just keep .width and .height + zero-initialized to open a default-sized window (what "default-size" + exactly means is platform-specific, but usually it's a size that covers + most of, but not all, of the display). + + All provided function callbacks will be called from the same thread, + but this may be different from the thread where sokol_main() was called. + + .init_cb (void (*)(void)) + This function is called once after the application window, + 3D rendering context and swap chain have been created. The + function takes no arguments and has no return value. + .frame_cb (void (*)(void)) + This is the per-frame callback, which is usually called 60 + times per second. This is where your application would update + most of its state and perform all rendering. + .cleanup_cb (void (*)(void)) + The cleanup callback is called once right before the application + quits. + .event_cb (void (*)(const sapp_event* event)) + The event callback is mainly for input handling, but is also + used to communicate other types of events to the application. Keep the + event_cb struct member zero-initialized if your application doesn't require + event handling. + + As you can see, those 'standard callbacks' don't have a user_data + argument, so any data that needs to be preserved between callbacks + must live in global variables. If keeping state in global variables + is not an option, there's an alternative set of callbacks with + an additional user_data pointer argument: + + .user_data (void*) + The user-data argument for the callbacks below + .init_userdata_cb (void (*)(void* user_data)) + .frame_userdata_cb (void (*)(void* user_data)) + .cleanup_userdata_cb (void (*)(void* user_data)) + .event_userdata_cb (void(*)(const sapp_event* event, void* user_data)) + + The function sapp_userdata() can be used to query the user_data + pointer provided in the sapp_desc struct. + + You can also call sapp_query_desc() to get a copy of the + original sapp_desc structure. + + NOTE that there's also an alternative compile mode where sokol_app.h + doesn't "hijack" the main() function. Search below for SOKOL_NO_ENTRY. + + --- Implement the initialization callback function (init_cb), this is called + once after the rendering surface, 3D API and swap chain have been + initialized by sokol_app. All sokol-app functions can be called + from inside the initialization callback, the most useful functions + at this point are: + + int sapp_width(void) + int sapp_height(void) + Returns the current width and height of the default framebuffer in pixels, + this may change from one frame to the next, and it may be different + from the initial size provided in the sapp_desc struct. + + float sapp_widthf(void) + float sapp_heightf(void) + These are alternatives to sapp_width() and sapp_height() which return + the default framebuffer size as float values instead of integer. This + may help to prevent casting back and forth between int and float + in more strongly typed languages than C and C++. + + double sapp_frame_duration(void) + Returns the frame duration in seconds averaged over a number of + frames to smooth out any jittering spikes. + + int sapp_color_format(void) + int sapp_depth_format(void) + The color and depth-stencil pixelformats of the default framebuffer, + as integer values which are compatible with sokol-gfx's + sg_pixel_format enum (so that they can be plugged directly in places + where sg_pixel_format is expected). Possible values are: + + 23 == SG_PIXELFORMAT_RGBA8 + 28 == SG_PIXELFORMAT_BGRA8 + 42 == SG_PIXELFORMAT_DEPTH + 43 == SG_PIXELFORMAT_DEPTH_STENCIL + + int sapp_sample_count(void) + Return the MSAA sample count of the default framebuffer. + + const void* sapp_metal_get_device(void) + const void* sapp_metal_get_current_drawable(void) + const void* sapp_metal_get_depth_stencil_texture(void) + const void* sapp_metal_get_msaa_color_texture(void) + If the Metal backend has been selected, these functions return pointers + to various Metal API objects required for rendering, otherwise + they return a null pointer. These void pointers are actually + Objective-C ids converted with a (ARC) __bridge cast so that + the ids can be tunneled through C code. Also note that the returned + pointers may change from one frame to the next, only the Metal device + object is guaranteed to stay the same. + + const void* sapp_macos_get_window(void) + On macOS, get the NSWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_ios_get_window(void) + On iOS, get the UIWindow object pointer, otherwise a null pointer. + Before being used as Objective-C object, the void* must be converted + back with a (ARC) __bridge cast. + + const void* sapp_d3d11_get_device(void) + const void* sapp_d3d11_get_device_context(void) + const void* sapp_d3d11_get_render_view(void) + const void* sapp_d3d11_get_resolve_view(void); + const void* sapp_d3d11_get_depth_stencil_view(void) + Similar to the sapp_metal_* functions, the sapp_d3d11_* functions + return pointers to D3D11 API objects required for rendering, + only if the D3D11 backend has been selected. Otherwise they + return a null pointer. Note that the returned pointers to the + render-target-view and depth-stencil-view may change from one + frame to the next! + + const void* sapp_win32_get_hwnd(void) + On Windows, get the window's HWND, otherwise a null pointer. The + HWND has been cast to a void pointer in order to be tunneled + through code which doesn't include Windows.h. + + const void* sapp_x11_get_window(void) + On Linux, get the X11 Window, otherwise a null pointer. The + Window has been cast to a void pointer in order to be tunneled + through code which doesn't include X11/Xlib.h. + + const void* sapp_x11_get_display(void) + On Linux, get the X11 Display, otherwise a null pointer. The + Display has been cast to a void pointer in order to be tunneled + through code which doesn't include X11/Xlib.h. + + const void* sapp_wgpu_get_device(void) + const void* sapp_wgpu_get_render_view(void) + const void* sapp_wgpu_get_resolve_view(void) + const void* sapp_wgpu_get_depth_stencil_view(void) + These are the WebGPU-specific functions to get the WebGPU + objects and values required for rendering. If sokol_app.h + is not compiled with SOKOL_WGPU, these functions return null. + + uint32_t sapp_gl_get_framebuffer(void) + This returns the 'default framebuffer' of the GL context. + Typically this will be zero. + + int sapp_gl_get_major_version(void) + int sapp_gl_get_minor_version(void) + bool sapp_gl_is_gles(void) + Returns the major and minor version of the GL context and + whether the GL context is a GLES context + + const void* sapp_android_get_native_activity(void); + On Android, get the native activity ANativeActivity pointer, otherwise + a null pointer. + + --- Implement the frame-callback function, this function will be called + on the same thread as the init callback, but might be on a different + thread than the sokol_main() function. Note that the size of + the rendering framebuffer might have changed since the frame callback + was called last. Call the functions sapp_width() and sapp_height() + each frame to get the current size. + + --- Optionally implement the event-callback to handle input events. + sokol-app provides the following type of input events: + - a 'virtual key' was pressed down or released + - a single text character was entered (provided as UTF-32 encoded + UNICODE code point) + - a mouse button was pressed down or released (left, right, middle) + - mouse-wheel or 2D scrolling events + - the mouse was moved + - the mouse has entered or left the application window boundaries + - low-level, portable multi-touch events (began, moved, ended, cancelled) + - the application window was resized, iconified or restored + - the application was suspended or restored (on mobile platforms) + - the user or application code has asked to quit the application + - a string was pasted to the system clipboard + - one or more files have been dropped onto the application window + + To explicitly 'consume' an event and prevent that the event is + forwarded for further handling to the operating system, call + sapp_consume_event() from inside the event handler (NOTE that + this behaviour is currently only implemented for some HTML5 + events, support for other platforms and event types will + be added as needed, please open a GitHub ticket and/or provide + a PR if needed). + + NOTE: Do *not* call any 3D API rendering functions in the event + callback function, since the 3D API context may not be active when the + event callback is called (it may work on some platforms and 3D APIs, + but not others, and the exact behaviour may change between + sokol-app versions). + + --- Implement the cleanup-callback function, this is called once + after the user quits the application (see the section + "APPLICATION QUIT" for detailed information on quitting + behaviour, and how to intercept a pending quit - for instance to show a + "Really Quit?" dialog box). Note that the cleanup-callback isn't + guaranteed to be called on the web and mobile platforms. + + MOUSE CURSOR TYPE AND VISIBILITY + ================================ + You can show and hide the mouse cursor with + + void sapp_show_mouse(bool show) + + And to get the current shown status: + + bool sapp_mouse_shown(void) + + NOTE that hiding the mouse cursor is different and independent from + the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when + active (MOUSE LOCK is described below). + + To change the mouse cursor to one of several predefined types, call + the function: + + void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) + + Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore + the standard look. + + To get the currently active mouse cursor type, call: + + sapp_mouse_cursor sapp_get_mouse_cursor(void) + + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) + ================================================ + In normal mouse mode, no mouse movement events are reported when the + mouse leaves the windows client area or hits the screen border (whether + it's one or the other depends on the platform), and the mouse move events + (SAPP_EVENTTYPE_MOUSE_MOVE) contain absolute mouse positions in + framebuffer pixels in the sapp_event items mouse_x and mouse_y, and + relative movement in framebuffer pixels in the sapp_event items mouse_dx + and mouse_dy. + + To get continuous mouse movement (also when the mouse leaves the window + client area or hits the screen border), activate mouse-lock mode + by calling: + + sapp_lock_mouse(true) + + When mouse lock is activated, the mouse pointer is hidden, the + reported absolute mouse position (sapp_event.mouse_x/y) appears + frozen, and the relative mouse movement in sapp_event.mouse_dx/dy + no longer has a direct relation to framebuffer pixels but instead + uses "raw mouse input" (what "raw mouse input" exactly means also + differs by platform). + + To deactivate mouse lock and return to normal mouse mode, call + + sapp_lock_mouse(false) + + And finally, to check if mouse lock is currently active, call + + if (sapp_mouse_locked()) { ... } + + Note that mouse-lock state may not change immediately after sapp_lock_mouse(true/false) + is called, instead on some platforms the actual state switch may be delayed + to the end of the current frame or even to a later frame. + + The mouse may also be unlocked automatically without calling sapp_lock_mouse(false), + most notably when the application window becomes inactive. + + On the web platform there are further restrictions to be aware of, caused + by the limitations of the HTML5 Pointer Lock API: + + - sapp_lock_mouse(true) can be called at any time, but it will + only take effect in a 'short-lived input event handler of a specific + type', meaning when one of the following events happens: + - SAPP_EVENTTYPE_MOUSE_DOWN + - SAPP_EVENTTYPE_MOUSE_UP + - SAPP_EVENTTYPE_MOUSE_SCROLL + - SAPP_EVENTTYPE_KEY_UP + - SAPP_EVENTTYPE_KEY_DOWN + - The mouse lock/unlock action on the web platform is asynchronous, + this means that sapp_mouse_locked() won't immediately return + the new status after calling sapp_lock_mouse(), instead the + reported status will only change when the pointer lock has actually + been activated or deactivated in the browser. + - On the web, mouse lock can be deactivated by the user at any time + by pressing the Esc key. When this happens, sokol_app.h behaves + the same as if sapp_lock_mouse(false) is called. + + For things like camera manipulation it's most straightforward to lock + and unlock the mouse right from the sokol_app.h event handler, for + instance the following code enters and leaves mouse lock when the + left mouse button is pressed and released, and then uses the relative + movement information to manipulate a camera (taken from the + cgltf-sapp.c sample in the sokol-samples repository + at https://github.com/floooh/sokol-samples): + + static void input(const sapp_event* ev) { + switch (ev->type) { + case SAPP_EVENTTYPE_MOUSE_DOWN: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(true); + } + break; + + case SAPP_EVENTTYPE_MOUSE_UP: + if (ev->mouse_button == SAPP_MOUSEBUTTON_LEFT) { + sapp_lock_mouse(false); + } + break; + + case SAPP_EVENTTYPE_MOUSE_MOVE: + if (sapp_mouse_locked()) { + cam_orbit(&state.camera, ev->mouse_dx * 0.25f, ev->mouse_dy * 0.25f); + } + break; + + default: + break; + } + } + + For a 'first person shooter mouse' the following code inside the sokol-app event handler + is recommended somewhere in your frame callback: + + if (!sapp_mouse_locked()) { + sapp_lock_mouse(true); + } + + CLIPBOARD SUPPORT + ================= + Applications can send and receive UTF-8 encoded text data from and to the + system clipboard. By default, clipboard support is disabled and + must be enabled at startup via the following sapp_desc struct + members: + + sapp_desc.enable_clipboard - set to true to enable clipboard support + sapp_desc.clipboard_size - size of the internal clipboard buffer in bytes + + Enabling the clipboard will dynamically allocate a clipboard buffer + for UTF-8 encoded text data of the requested size in bytes, the default + size is 8 KBytes. Strings that don't fit into the clipboard buffer + (including the terminating zero) will be silently clipped, so it's + important that you provide a big enough clipboard size for your + use case. + + To send data to the clipboard, call sapp_set_clipboard_string() with + a pointer to an UTF-8 encoded, null-terminated C-string. + + NOTE that on the HTML5 platform, sapp_set_clipboard_string() must be + called from inside a 'short-lived event handler', and there are a few + other HTML5-specific caveats to workaround. You'll basically have to + tinker until it works in all browsers :/ (maybe the situation will + improve when all browsers agree on and implement the new + HTML5 navigator.clipboard API). + + To get data from the clipboard, check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED + event in your event handler function, and then call sapp_get_clipboard_string() + to obtain the pasted UTF-8 encoded text. + + NOTE that behaviour of sapp_get_clipboard_string() is slightly different + depending on platform: + + - on the HTML5 platform, the internal clipboard buffer will only be updated + right before the SAPP_EVENTTYPE_CLIPBOARD_PASTED event is sent, + and sapp_get_clipboard_string() will simply return the current content + of the clipboard buffer + - on 'native' platforms, the call to sapp_get_clipboard_string() will + update the internal clipboard buffer with the most recent data + from the system clipboard + + Portable code should check for the SAPP_EVENTTYPE_CLIPBOARD_PASTED event, + and then call sapp_get_clipboard_string() right in the event handler. + + The SAPP_EVENTTYPE_CLIPBOARD_PASTED event will be generated by sokol-app + as follows: + + - on macOS: when the Cmd+V key is pressed down + - on HTML5: when the browser sends a 'paste' event to the global 'window' object + - on all other platforms: when the Ctrl+V key is pressed down + + DRAG AND DROP SUPPORT + ===================== + PLEASE NOTE: the drag'n'drop feature works differently on WASM/HTML5 + and on the native desktop platforms (Win32, Linux and macOS) because + of security-related restrictions in the HTML5 drag'n'drop API. The + WASM/HTML5 specifics are described at the end of this documentation + section: + + Like clipboard support, drag'n'drop support must be explicitly enabled + at startup in the sapp_desc struct. + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + ... + }; + } + + You can also adjust the maximum number of files that are accepted + in a drop operation, and the maximum path length in bytes if needed: + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .enable_dragndrop = true, // default is false + .max_dropped_files = 8, // default is 1 + .max_dropped_file_path_length = 8192, // in bytes, default is 2048 + ... + }; + } + + When drag'n'drop is enabled, the event callback will be invoked with an + event of type SAPP_EVENTTYPE_FILES_DROPPED whenever the user drops files on + the application window. + + After the SAPP_EVENTTYPE_FILES_DROPPED is received, you can query the + number of dropped files, and their absolute paths by calling separate + functions: + + void on_event(const sapp_event* ev) { + if (ev->type == SAPP_EVENTTYPE_FILES_DROPPED) { + + // the mouse position where the drop happened + float x = ev->mouse_x; + float y = ev->mouse_y; + + // get the number of files and their paths like this: + const int num_dropped_files = sapp_get_num_dropped_files(); + for (int i = 0; i < num_dropped_files; i++) { + const char* path = sapp_get_dropped_file_path(i); + ... + } + } + } + + The returned file paths are UTF-8 encoded strings. + + You can call sapp_get_num_dropped_files() and sapp_get_dropped_file_path() + anywhere, also outside the event handler callback, but be aware that the + file path strings will be overwritten with the next drop operation. + + In any case, sapp_get_dropped_file_path() will never return a null pointer, + instead an empty string "" will be returned if the drag'n'drop feature + hasn't been enabled, the last drop-operation failed, or the file path index + is out of range. + + Drag'n'drop caveats: + + - if more files are dropped in a single drop-action + than sapp_desc.max_dropped_files, the additional + files will be silently ignored + - if any of the file paths is longer than + sapp_desc.max_dropped_file_path_length (in number of bytes, after UTF-8 + encoding) the entire drop operation will be silently ignored (this + needs some sort of error feedback in the future) + - no mouse positions are reported while the drag is in + process, this may change in the future + + Drag'n'drop on HTML5/WASM: + + The HTML5 drag'n'drop API doesn't return file paths, but instead + black-box 'file objects' which must be used to load the content + of dropped files. This is the reason why sokol_app.h adds two + HTML5-specific functions to the drag'n'drop API: + + uint32_t sapp_html5_get_dropped_file_size(int index) + Returns the size in bytes of a dropped file. + + void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) + Asynchronously loads the content of a dropped file into a + provided memory buffer (which must be big enough to hold + the file content) + + To start loading the first dropped file after an SAPP_EVENTTYPE_FILES_DROPPED + event is received: + + sapp_html5_fetch_dropped_file(&(sapp_html5_fetch_request){ + .dropped_file_index = 0, + .callback = fetch_cb + .buffer = { + .ptr = buf, + .size = sizeof(buf) + }, + .user_data = ... + }); + + Make sure that the memory pointed to by 'buf' stays valid until the + callback function is called! + + As result of the asynchronous loading operation (no matter if succeeded or + failed) the 'fetch_cb' function will be called: + + void fetch_cb(const sapp_html5_fetch_response* response) { + // IMPORTANT: check if the loading operation actually succeeded: + if (response->succeeded) { + // the size of the loaded file: + const size_t num_bytes = response->data.size; + // and the pointer to the data (same as 'buf' in the fetch-call): + const void* ptr = response->data.ptr; + } + else { + // on error check the error code: + switch (response->error_code) { + case SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL: + ... + break; + case SAPP_HTML5_FETCH_ERROR_OTHER: + ... + break; + } + } + } + + Check the droptest-sapp example for a real-world example which works + both on native platforms and the web: + + https://github.com/floooh/sokol-samples/blob/master/sapp/droptest-sapp.c + + HIGH-DPI RENDERING + ================== + You can set the sapp_desc.high_dpi flag during initialization to request + a full-resolution framebuffer on HighDPI displays. The default behaviour + is sapp_desc.high_dpi=false, this means that the application will + render to a lower-resolution framebuffer on HighDPI displays and the + rendered content will be upscaled by the window system composer. + + In a HighDPI scenario, you still request the same window size during + sokol_main(), but the framebuffer sizes returned by sapp_width() + and sapp_height() will be scaled up according to the DPI scaling + ratio. + + Note that on some platforms the DPI scaling factor may change at any + time (for instance when a window is moved from a high-dpi display + to a low-dpi display). + + To query the current DPI scaling factor, call the function: + + float sapp_dpi_scale(void); + + For instance on a Retina Mac, returning the following sapp_desc + struct from sokol_main(): + + sapp_desc sokol_main(void) { + return (sapp_desc) { + .width = 640, + .height = 480, + .high_dpi = true, + ... + }; + } + + ...the functions the functions sapp_width(), sapp_height() + and sapp_dpi_scale() will return the following values: + + sapp_width: 1280 + sapp_height: 960 + sapp_dpi_scale: 2.0 + + If the high_dpi flag is false, or you're not running on a Retina display, + the values would be: + + sapp_width: 640 + sapp_height: 480 + sapp_dpi_scale: 1.0 + + If the window is moved from the Retina display to a low-dpi external display, + the values would change as follows: + + sapp_width: 1280 => 640 + sapp_height: 960 => 480 + sapp_dpi_scale: 2.0 => 1.0 + + Currently there is no event associated with a DPI change, but an + SAPP_EVENTTYPE_RESIZED will be sent as a side effect of the + framebuffer size changing. + + Per-monitor DPI is currently supported on macOS and Windows. + + APPLICATION QUIT + ================ + Without special quit handling, a sokol_app.h application will quit + 'gracefully' when the user clicks the window close-button unless a + platform's application model prevents this (e.g. on web or mobile). + 'Graceful exit' means that the application-provided cleanup callback will + be called before the application quits. + + On native desktop platforms sokol_app.h provides more control over the + application-quit-process. It's possible to initiate a 'programmatic quit' + from the application code, and a quit initiated by the application user can + be intercepted (for instance to show a custom dialog box). + + This 'programmatic quit protocol' is implemented through 3 functions + and 1 event: + + - sapp_quit(): This function simply quits the application without + giving the user a chance to intervene. Usually this might + be called when the user clicks the 'Ok' button in a 'Really Quit?' + dialog box + - sapp_request_quit(): Calling sapp_request_quit() will send the + event SAPP_EVENTTYPE_QUIT_REQUESTED to the applications event handler + callback, giving the user code a chance to intervene and cancel the + pending quit process (for instance to show a 'Really Quit?' dialog + box). If the event handler callback does nothing, the application + will be quit as usual. To prevent this, call the function + sapp_cancel_quit() from inside the event handler. + - sapp_cancel_quit(): Cancels a pending quit request, either initiated + by the user clicking the window close button, or programmatically + by calling sapp_request_quit(). The only place where calling this + function makes sense is from inside the event handler callback when + the SAPP_EVENTTYPE_QUIT_REQUESTED event has been received. + - SAPP_EVENTTYPE_QUIT_REQUESTED: this event is sent when the user + clicks the window's close button or application code calls the + sapp_request_quit() function. The event handler callback code can handle + this event by calling sapp_cancel_quit() to cancel the quit. + If the event is ignored, the application will quit as usual. + + On the web platform, the quit behaviour differs from native platforms, + because of web-specific restrictions: + + A `programmatic quit` initiated by calling sapp_quit() or + sapp_request_quit() will work as described above: the cleanup callback is + called, platform-specific cleanup is performed (on the web + this means that JS event handlers are unregistered), and then + the request-animation-loop will be exited. However that's all. The + web page itself will continue to exist (e.g. it's not possible to + programmatically close the browser tab). + + On the web it's also not possible to run custom code when the user + closes a browser tab, so it's not possible to prevent this with a + fancy custom dialog box. + + Instead the standard "Leave Site?" dialog box can be activated (or + deactivated) with the following function: + + sapp_html5_ask_leave_site(bool ask); + + The initial state of the associated internal flag can be provided + at startup via sapp_desc.html5_ask_leave_site. + + This feature should only be used sparingly in critical situations - for + instance when the user would loose data - since popping up modal dialog + boxes is considered quite rude in the web world. Note that there's no way + to customize the content of this dialog box or run any code as a result + of the user's decision. Also note that the user must have interacted with + the site before the dialog box will appear. These are all security measures + to prevent fishing. + + The Dear ImGui HighDPI sample contains example code of how to + implement a 'Really Quit?' dialog box with Dear ImGui (native desktop + platforms only), and for showing the hardwired "Leave Site?" dialog box + when running on the web platform: + + https://floooh.github.io/sokol-html5/wasm/imgui-highdpi-sapp.html + + FULLSCREEN + ========== + If the sapp_desc.fullscreen flag is true, sokol-app will try to create + a fullscreen window on platforms with a 'proper' window system + (mobile devices will always use fullscreen). The implementation details + depend on the target platform, in general sokol-app will use a + 'soft approach' which doesn't interfere too much with the platform's + window system (for instance borderless fullscreen window instead of + a 'real' fullscreen mode). Such details might change over time + as sokol-app is adapted for different needs. + + The most important effect of fullscreen mode to keep in mind is that + the requested canvas width and height will be ignored for the initial + window size, calling sapp_width() and sapp_height() will instead return + the resolution of the fullscreen canvas (however the provided size + might still be used for the non-fullscreen window, in case the user can + switch back from fullscreen- to windowed-mode). + + To toggle fullscreen mode programmatically, call sapp_toggle_fullscreen(). + + To check if the application window is currently in fullscreen mode, + call sapp_is_fullscreen(). + + WINDOW ICON SUPPORT + =================== + Some sokol_app.h backends allow to change the window icon programmatically: + + - on Win32: the small icon in the window's title bar, and the + bigger icon in the task bar + - on Linux: highly dependent on the used window manager, but usually + the window's title bar icon and/or the task bar icon + - on HTML5: the favicon shown in the page's browser tab + - on macOS: the application icon shown in the dock, but only + for currently running applications + + NOTE that it is not possible to set the actual application icon which is + displayed by the operating system on the desktop or 'home screen'. Those + icons must be provided 'traditionally' through operating-system-specific + resources which are associated with the application (sokol_app.h might + later support setting the window icon from platform specific resource data + though). + + There are two ways to set the window icon: + + - at application start in the sokol_main() function by initializing + the sapp_desc.icon nested struct + - or later by calling the function sapp_set_icon() + + As a convenient shortcut, sokol_app.h comes with a builtin default-icon + (a rainbow-colored 'S', which at least looks a bit better than the Windows + default icon for applications), which can be activated like this: + + At startup in sokol_main(): + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon.sokol_default = true + }; + } + + Or later by calling: + + sapp_set_icon(&(sapp_icon_desc){ .sokol_default = true }); + + NOTE that a completely zero-initialized sapp_icon_desc struct will not + update the window icon in any way. This is an 'escape hatch' so that you + can handle the window icon update yourself (or if you do this already, + sokol_app.h won't get in your way, in this case just leave the + sapp_desc.icon struct zero-initialized). + + Providing your own icon images works exactly like in GLFW (down to the + data format): + + You provide one or more 'candidate images' in different sizes, and the + sokol_app.h platform backends pick the best match for the specific backend + and icon type. + + For each candidate image, you need to provide: + + - the width in pixels + - the height in pixels + - and the actual pixel data in RGBA8 pixel format (e.g. 0xFFCC8844 + on a little-endian CPU means: alpha=0xFF, blue=0xCC, green=0x88, red=0x44) + + For instance, if you have 3 candidate images (small, medium, big) of + sizes 16x16, 32x32 and 64x64 the corresponding sapp_icon_desc struct is setup + like this: + + // the actual pixel data (RGBA8, origin top-left) + const uint32_t small[16][16] = { ... }; + const uint32_t medium[32][32] = { ... }; + const uint32_t big[64][64] = { ... }; + + const sapp_icon_desc icon_desc = { + .images = { + { .width = 16, .height = 16, .pixels = SAPP_RANGE(small) }, + { .width = 32, .height = 32, .pixels = SAPP_RANGE(medium) }, + // ...or without the SAPP_RANGE helper macro: + { .width = 64, .height = 64, .pixels = { .ptr=big, .size=sizeof(big) } } + } + }; + + An sapp_icon_desc struct initialized like this can then either be applied + at application start in sokol_main: + + sapp_desc sokol_main(...) { + return (sapp_desc){ + ... + icon = icon_desc + }; + } + + ...or later by calling sapp_set_icon(): + + sapp_set_icon(&icon_desc); + + Some window icon caveats: + + - once the window icon has been updated, there's no way to go back to + the platform's default icon, this is because some platforms (Linux + and HTML5) don't switch the icon visual back to the default even if + the custom icon is deleted or removed + - on HTML5, if the sokol_app.h icon doesn't show up in the browser + tab, check that there's no traditional favicon 'link' element + is defined in the page's index.html, sokol_app.h will only + append a new favicon link element, but not delete any manually + defined favicon in the page + + For an example and test of the window icon feature, check out the + 'icon-sapp' sample on the sokol-samples git repository. + + ONSCREEN KEYBOARD + ================= + On some platforms which don't provide a physical keyboard, sokol-app + can display the platform's integrated onscreen keyboard for text + input. To request that the onscreen keyboard is shown, call + + sapp_show_keyboard(true); + + Likewise, to hide the keyboard call: + + sapp_show_keyboard(false); + + Note that onscreen keyboard functionality is no longer supported + on the browser platform (the previous hacks and workarounds to make browser + keyboards work for on web applications that don't use HTML UIs + never really worked across browsers). + + INPUT EVENT BUBBLING ON THE WEB PLATFORM + ======================================== + By default, input event bubbling on the web platform is configured in + a way that makes the most sense for 'full-canvas' apps that cover the + entire browser client window area: + + - mouse, touch and wheel events do not bubble up, this prevents various + ugly side events, like: + - HTML text overlays being selected on double- or triple-click into + the canvas + - 'scroll bumping' even when the canvas covers the entire client area + - key_up/down events for 'character keys' *do* bubble up (otherwise + the browser will not generate UNICODE character events) + - all other key events *do not* bubble up by default (this prevents side effects + like F1 opening help, or F7 starting 'caret browsing') + - character events do not bubble up (although I haven't noticed any side effects + otherwise) + + Event bubbling can be enabled for input event categories during initialization + in the sapp_desc struct: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + //... + .html5_bubble_mouse_events = true, + .html5_bubble_touch_events = true, + .html5_bubble_wheel_events = true, + .html5_bubble_key_events = true, + .html5_bubble_char_events = true, + }; + } + + This basically opens the floodgates and lets *all* input events bubble up to the browser. + + To prevent individual events from bubbling, call sapp_consume_event() from within + the sokol_app.h event callback when that specific event is reported. + + + SETTING THE CANVAS OBJECT ON THE WEB PLATFORM + ============================================= + On the web, sokol_app.h and the Emscripten SDK functions need to find + the WebGL/WebGPU canvas intended for rendering and attaching event + handlers. This can happen in four ways: + + 1. do nothing and just set the id of the canvas object to 'canvas' (preferred) + 2. via a CSS Selector string (preferred) + 3. by setting the `Module.canvas` property to the canvas object + 4. by adding the canvas object to the global variable `specialHTMLTargets[]` + (this is a special variable used by the Emscripten runtime to lookup + event target objects for which document.querySelector() cannot be used) + + The easiest way is to just name your canvas object 'canvas': + + + + This works because the default css selector string used by sokol_app.h + is '#canvas'. + + If you name your canvas differently, you need to communicate that name to + sokol_app.h via `sapp_desc.html5_canvas_selector` as a regular css selector + string that's compatible with `document.querySelector()`. E.g. if your canvas + object looks like this: + + + + The `sapp_desc.html5_canvas_selector` string must be set to '#bla': + + .html5_canvas_selector = "#bla" + + If the canvas object cannot be looked up via `document.querySelector()` you + need to use one of the alternative methods, both involve the special + Emscripten runtime `Module` object which is usually setup in the index.html + like this before the WASM blob is loaded and instantiated: + + + + The first option is to set the `Module.canvas` property to your canvas object: + + + + When sokol_app.h initializes, it will check the global Module object whether + a `Module.canvas` property exists and is an object. This method will add + a new entry to the `specialHTMLTargets[]` object + + The other option is to add the canvas under a name chosen by you to the + special `specialHTMLTargets[]` map, which is used by the Emscripten runtime + to lookup 'event target objects' which are not visible to `document.querySelector()`. + Note that `specialHTMLTargets[]` must be updated after the Emscripten runtime + has started but before the WASM code is running. A good place for this is + the special `Module.preRun` array in index.html: + + + + In that case, pass the same string to sokol_app.h which is used as key + in the specialHTMLTargets[] map: + + .html5_canvas_selector = "my_canvas" + + If sokol_app.h can't find your canvas for some reason check for warning + messages on the browser console. + + + OPTIONAL: DON'T HIJACK main() (#define SOKOL_NO_ENTRY) + ====================================================== + NOTE: SOKOL_NO_ENTRY and sapp_run() is currently not supported on Android. + + In its default configuration, sokol_app.h "hijacks" the platform's + standard main() function. This was done because different platforms + have different entry point conventions which are not compatible with + C's main() (for instance WinMain on Windows has completely different + arguments). However, this "main hijacking" posed a problem for + usage scenarios like integrating sokol_app.h with other languages than + C or C++, so an alternative SOKOL_NO_ENTRY mode has been added + in which the user code provides the platform's main function: + + - define SOKOL_NO_ENTRY before including the sokol_app.h implementation + - do *not* provide a sokol_main() function + - instead provide the standard main() function of the platform + - from the main function, call the function ```sapp_run()``` which + takes a pointer to an ```sapp_desc``` structure. + - from here on```sapp_run()``` takes over control and calls the provided + init-, frame-, event- and cleanup-callbacks just like in the default model. + + sapp_run() behaves differently across platforms: + + - on some platforms, sapp_run() will return when the application quits + - on other platforms, sapp_run() will never return, even when the + application quits (the operating system is free to simply terminate + the application at any time) + - on Emscripten specifically, sapp_run() will return immediately while + the frame callback keeps being called + + This different behaviour of sapp_run() essentially means that there shouldn't + be any code *after* sapp_run(), because that may either never be called, or in + case of Emscripten will be called at an unexpected time (at application start). + + An application also should not depend on the cleanup-callback being called + when cross-platform compatibility is required. + + Since sapp_run() returns immediately on Emscripten you shouldn't activate + the 'EXIT_RUNTIME' linker option (this is disabled by default when compiling + for the browser target), since the C/C++ exit runtime would be called immediately at + application start, causing any global objects to be destroyed and global + variables to be zeroed. + + WINDOWS CONSOLE OUTPUT + ====================== + On Windows, regular windowed applications don't show any stdout/stderr text + output, which can be a bit of a hassle for printf() debugging or generally + logging text to the console. Also, console output by default uses a local + codepage setting and thus international UTF-8 encoded text is printed + as garbage. + + To help with these issues, sokol_app.h can be configured at startup + via the following Windows-specific sapp_desc flags: + + sapp_desc.win32_console_utf8 (default: false) + When set to true, the output console codepage will be switched + to UTF-8 (and restored to the original codepage on exit) + + sapp_desc.win32_console_attach (default: false) + When set to true, stdout and stderr will be attached to the + console of the parent process (if the parent process actually + has a console). This means that if the application was started + in a command line window, stdout and stderr output will be printed + to the terminal, just like a regular command line program. But if + the application is started via double-click, it will behave like + a regular UI application, and stdout/stderr will not be visible. + + sapp_desc.win32_console_create (default: false) + When set to true, a new console window will be created and + stdout/stderr will be redirected to that console window. It + doesn't matter if the application is started from the command + line or via double-click. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }; + } + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_app.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger.func = slog_func, + }; + } + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sapp' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-app like this: + + sapp_desc sokol_main(int argc, char* argv[]) { + return (sapp_desc) { + ... + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }; + } + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + TEMP NOTE DUMP + ============== + - sapp_desc needs a bool whether to initialize depth-stencil surface + - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy + at the latest but should do it earlier, in onStop, as an app is "killable" after onStop + on Android Honeycomb and later (it can't be done at the moment as the app may be started + again after onStop and the sokol lifecycle does not yet handle context teardown/bringup) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_APP_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_APP_API_DECL) +#define SOKOL_APP_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_APP_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_APP_IMPL) +#define SOKOL_APP_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_APP_API_DECL __declspec(dllimport) +#else +#define SOKOL_APP_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* misc constants */ +enum { + SAPP_MAX_TOUCHPOINTS = 8, + SAPP_MAX_MOUSEBUTTONS = 3, + SAPP_MAX_KEYCODES = 512, + SAPP_MAX_ICONIMAGES = 8, +}; + +/* + sapp_event_type + + The type of event that's passed to the event handler callback + in the sapp_event.type field. These are not just "traditional" + input events, but also notify the application about state changes + or other user-invoked actions. +*/ +typedef enum sapp_event_type { + SAPP_EVENTTYPE_INVALID, + SAPP_EVENTTYPE_KEY_DOWN, + SAPP_EVENTTYPE_KEY_UP, + SAPP_EVENTTYPE_CHAR, + SAPP_EVENTTYPE_MOUSE_DOWN, + SAPP_EVENTTYPE_MOUSE_UP, + SAPP_EVENTTYPE_MOUSE_SCROLL, + SAPP_EVENTTYPE_MOUSE_MOVE, + SAPP_EVENTTYPE_MOUSE_ENTER, + SAPP_EVENTTYPE_MOUSE_LEAVE, + SAPP_EVENTTYPE_TOUCHES_BEGAN, + SAPP_EVENTTYPE_TOUCHES_MOVED, + SAPP_EVENTTYPE_TOUCHES_ENDED, + SAPP_EVENTTYPE_TOUCHES_CANCELLED, + SAPP_EVENTTYPE_RESIZED, + SAPP_EVENTTYPE_ICONIFIED, + SAPP_EVENTTYPE_RESTORED, + SAPP_EVENTTYPE_FOCUSED, + SAPP_EVENTTYPE_UNFOCUSED, + SAPP_EVENTTYPE_SUSPENDED, + SAPP_EVENTTYPE_RESUMED, + SAPP_EVENTTYPE_QUIT_REQUESTED, + SAPP_EVENTTYPE_CLIPBOARD_PASTED, + SAPP_EVENTTYPE_FILES_DROPPED, + _SAPP_EVENTTYPE_NUM, + _SAPP_EVENTTYPE_FORCE_U32 = 0x7FFFFFFF +} sapp_event_type; + +/* + sapp_keycode + + The 'virtual keycode' of a KEY_DOWN or KEY_UP event in the + struct field sapp_event.key_code. + + Note that the keycode values are identical with GLFW. +*/ +typedef enum sapp_keycode { + SAPP_KEYCODE_INVALID = 0, + SAPP_KEYCODE_SPACE = 32, + SAPP_KEYCODE_APOSTROPHE = 39, /* ' */ + SAPP_KEYCODE_COMMA = 44, /* , */ + SAPP_KEYCODE_MINUS = 45, /* - */ + SAPP_KEYCODE_PERIOD = 46, /* . */ + SAPP_KEYCODE_SLASH = 47, /* / */ + SAPP_KEYCODE_0 = 48, + SAPP_KEYCODE_1 = 49, + SAPP_KEYCODE_2 = 50, + SAPP_KEYCODE_3 = 51, + SAPP_KEYCODE_4 = 52, + SAPP_KEYCODE_5 = 53, + SAPP_KEYCODE_6 = 54, + SAPP_KEYCODE_7 = 55, + SAPP_KEYCODE_8 = 56, + SAPP_KEYCODE_9 = 57, + SAPP_KEYCODE_SEMICOLON = 59, /* ; */ + SAPP_KEYCODE_EQUAL = 61, /* = */ + SAPP_KEYCODE_A = 65, + SAPP_KEYCODE_B = 66, + SAPP_KEYCODE_C = 67, + SAPP_KEYCODE_D = 68, + SAPP_KEYCODE_E = 69, + SAPP_KEYCODE_F = 70, + SAPP_KEYCODE_G = 71, + SAPP_KEYCODE_H = 72, + SAPP_KEYCODE_I = 73, + SAPP_KEYCODE_J = 74, + SAPP_KEYCODE_K = 75, + SAPP_KEYCODE_L = 76, + SAPP_KEYCODE_M = 77, + SAPP_KEYCODE_N = 78, + SAPP_KEYCODE_O = 79, + SAPP_KEYCODE_P = 80, + SAPP_KEYCODE_Q = 81, + SAPP_KEYCODE_R = 82, + SAPP_KEYCODE_S = 83, + SAPP_KEYCODE_T = 84, + SAPP_KEYCODE_U = 85, + SAPP_KEYCODE_V = 86, + SAPP_KEYCODE_W = 87, + SAPP_KEYCODE_X = 88, + SAPP_KEYCODE_Y = 89, + SAPP_KEYCODE_Z = 90, + SAPP_KEYCODE_LEFT_BRACKET = 91, /* [ */ + SAPP_KEYCODE_BACKSLASH = 92, /* \ */ + SAPP_KEYCODE_RIGHT_BRACKET = 93, /* ] */ + SAPP_KEYCODE_GRAVE_ACCENT = 96, /* ` */ + SAPP_KEYCODE_WORLD_1 = 161, /* non-US #1 */ + SAPP_KEYCODE_WORLD_2 = 162, /* non-US #2 */ + SAPP_KEYCODE_ESCAPE = 256, + SAPP_KEYCODE_ENTER = 257, + SAPP_KEYCODE_TAB = 258, + SAPP_KEYCODE_BACKSPACE = 259, + SAPP_KEYCODE_INSERT = 260, + SAPP_KEYCODE_DELETE = 261, + SAPP_KEYCODE_RIGHT = 262, + SAPP_KEYCODE_LEFT = 263, + SAPP_KEYCODE_DOWN = 264, + SAPP_KEYCODE_UP = 265, + SAPP_KEYCODE_PAGE_UP = 266, + SAPP_KEYCODE_PAGE_DOWN = 267, + SAPP_KEYCODE_HOME = 268, + SAPP_KEYCODE_END = 269, + SAPP_KEYCODE_CAPS_LOCK = 280, + SAPP_KEYCODE_SCROLL_LOCK = 281, + SAPP_KEYCODE_NUM_LOCK = 282, + SAPP_KEYCODE_PRINT_SCREEN = 283, + SAPP_KEYCODE_PAUSE = 284, + SAPP_KEYCODE_F1 = 290, + SAPP_KEYCODE_F2 = 291, + SAPP_KEYCODE_F3 = 292, + SAPP_KEYCODE_F4 = 293, + SAPP_KEYCODE_F5 = 294, + SAPP_KEYCODE_F6 = 295, + SAPP_KEYCODE_F7 = 296, + SAPP_KEYCODE_F8 = 297, + SAPP_KEYCODE_F9 = 298, + SAPP_KEYCODE_F10 = 299, + SAPP_KEYCODE_F11 = 300, + SAPP_KEYCODE_F12 = 301, + SAPP_KEYCODE_F13 = 302, + SAPP_KEYCODE_F14 = 303, + SAPP_KEYCODE_F15 = 304, + SAPP_KEYCODE_F16 = 305, + SAPP_KEYCODE_F17 = 306, + SAPP_KEYCODE_F18 = 307, + SAPP_KEYCODE_F19 = 308, + SAPP_KEYCODE_F20 = 309, + SAPP_KEYCODE_F21 = 310, + SAPP_KEYCODE_F22 = 311, + SAPP_KEYCODE_F23 = 312, + SAPP_KEYCODE_F24 = 313, + SAPP_KEYCODE_F25 = 314, + SAPP_KEYCODE_KP_0 = 320, + SAPP_KEYCODE_KP_1 = 321, + SAPP_KEYCODE_KP_2 = 322, + SAPP_KEYCODE_KP_3 = 323, + SAPP_KEYCODE_KP_4 = 324, + SAPP_KEYCODE_KP_5 = 325, + SAPP_KEYCODE_KP_6 = 326, + SAPP_KEYCODE_KP_7 = 327, + SAPP_KEYCODE_KP_8 = 328, + SAPP_KEYCODE_KP_9 = 329, + SAPP_KEYCODE_KP_DECIMAL = 330, + SAPP_KEYCODE_KP_DIVIDE = 331, + SAPP_KEYCODE_KP_MULTIPLY = 332, + SAPP_KEYCODE_KP_SUBTRACT = 333, + SAPP_KEYCODE_KP_ADD = 334, + SAPP_KEYCODE_KP_ENTER = 335, + SAPP_KEYCODE_KP_EQUAL = 336, + SAPP_KEYCODE_LEFT_SHIFT = 340, + SAPP_KEYCODE_LEFT_CONTROL = 341, + SAPP_KEYCODE_LEFT_ALT = 342, + SAPP_KEYCODE_LEFT_SUPER = 343, + SAPP_KEYCODE_RIGHT_SHIFT = 344, + SAPP_KEYCODE_RIGHT_CONTROL = 345, + SAPP_KEYCODE_RIGHT_ALT = 346, + SAPP_KEYCODE_RIGHT_SUPER = 347, + SAPP_KEYCODE_MENU = 348, +} sapp_keycode; + +/* + Android specific 'tool type' enum for touch events. This lets the + application check what type of input device was used for + touch events. + + NOTE: the values must remain in sync with the corresponding + Android SDK type, so don't change those. + + See https://developer.android.com/reference/android/view/MotionEvent#TOOL_TYPE_UNKNOWN +*/ +typedef enum sapp_android_tooltype { + SAPP_ANDROIDTOOLTYPE_UNKNOWN = 0, // TOOL_TYPE_UNKNOWN + SAPP_ANDROIDTOOLTYPE_FINGER = 1, // TOOL_TYPE_FINGER + SAPP_ANDROIDTOOLTYPE_STYLUS = 2, // TOOL_TYPE_STYLUS + SAPP_ANDROIDTOOLTYPE_MOUSE = 3, // TOOL_TYPE_MOUSE +} sapp_android_tooltype; + +/* + sapp_touchpoint + + Describes a single touchpoint in a multitouch event (TOUCHES_BEGAN, + TOUCHES_MOVED, TOUCHES_ENDED). + + Touch points are stored in the nested array sapp_event.touches[], + and the number of touches is stored in sapp_event.num_touches. +*/ +typedef struct sapp_touchpoint { + uintptr_t identifier; + float pos_x; + float pos_y; + sapp_android_tooltype android_tooltype; // only valid on Android + bool changed; +} sapp_touchpoint; + +/* + sapp_mousebutton + + The currently pressed mouse button in the events MOUSE_DOWN + and MOUSE_UP, stored in the struct field sapp_event.mouse_button. +*/ +typedef enum sapp_mousebutton { + SAPP_MOUSEBUTTON_LEFT = 0x0, + SAPP_MOUSEBUTTON_RIGHT = 0x1, + SAPP_MOUSEBUTTON_MIDDLE = 0x2, + SAPP_MOUSEBUTTON_INVALID = 0x100, +} sapp_mousebutton; + +/* + These are currently pressed modifier keys (and mouse buttons) which are + passed in the event struct field sapp_event.modifiers. +*/ +enum { + SAPP_MODIFIER_SHIFT = 0x1, // left or right shift key + SAPP_MODIFIER_CTRL = 0x2, // left or right control key + SAPP_MODIFIER_ALT = 0x4, // left or right alt key + SAPP_MODIFIER_SUPER = 0x8, // left or right 'super' key + SAPP_MODIFIER_LMB = 0x100, // left mouse button + SAPP_MODIFIER_RMB = 0x200, // right mouse button + SAPP_MODIFIER_MMB = 0x400, // middle mouse button +}; + +/* + sapp_event + + This is an all-in-one event struct passed to the event handler + user callback function. Note that it depends on the event + type what struct fields actually contain useful values, so you + should first check the event type before reading other struct + fields. +*/ +typedef struct sapp_event { + uint64_t frame_count; // current frame counter, always valid, useful for checking if two events were issued in the same frame + sapp_event_type type; // the event type, always valid + sapp_keycode key_code; // the virtual key code, only valid in KEY_UP, KEY_DOWN + uint32_t char_code; // the UTF-32 character code, only valid in CHAR events + bool key_repeat; // true if this is a key-repeat event, valid in KEY_UP, KEY_DOWN and CHAR + uint32_t modifiers; // current modifier keys, valid in all key-, char- and mouse-events + sapp_mousebutton mouse_button; // mouse button that was pressed or released, valid in MOUSE_DOWN, MOUSE_UP + float mouse_x; // current horizontal mouse position in pixels, always valid except during mouse lock + float mouse_y; // current vertical mouse position in pixels, always valid except during mouse lock + float mouse_dx; // relative horizontal mouse movement since last frame, always valid + float mouse_dy; // relative vertical mouse movement since last frame, always valid + float scroll_x; // horizontal mouse wheel scroll distance, valid in MOUSE_SCROLL events + float scroll_y; // vertical mouse wheel scroll distance, valid in MOUSE_SCROLL events + int num_touches; // number of valid items in the touches[] array + sapp_touchpoint touches[SAPP_MAX_TOUCHPOINTS]; // current touch points, valid in TOUCHES_BEGIN, TOUCHES_MOVED, TOUCHES_ENDED + int window_width; // current window- and framebuffer sizes in pixels, always valid + int window_height; + int framebuffer_width; // = window_width * dpi_scale + int framebuffer_height; // = window_height * dpi_scale +} sapp_event; + +/* + sg_range + + A general pointer/size-pair struct and constructor macros for passing binary blobs + into sokol_app.h. +*/ +typedef struct sapp_range { + const void* ptr; + size_t size; +} sapp_range; +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ +#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ +#endif +#if defined(__cplusplus) +#define SAPP_RANGE(x) sapp_range{ &x, sizeof(x) } +#else +#define SAPP_RANGE(x) (sapp_range){ &x, sizeof(x) } +#endif + +/* + sapp_image_desc + + This is used to describe image data to sokol_app.h (at first, window + icons, later maybe cursor images). + + Note that the actual image pixel format depends on the use case: + + - window icon pixels are RGBA8 +*/ +typedef struct sapp_image_desc { + int width; + int height; + sapp_range pixels; +} sapp_image_desc; + +/* + sapp_icon_desc + + An icon description structure for use in sapp_desc.icon and + sapp_set_icon(). + + When setting a custom image, the application can provide a number of + candidates differing in size, and sokol_app.h will pick the image(s) + closest to the size expected by the platform's window system. + + To set sokol-app's default icon, set .sokol_default to true. + + Otherwise provide candidate images of different sizes in the + images[] array. + + If both the sokol_default flag is set to true, any image candidates + will be ignored and the sokol_app.h default icon will be set. +*/ +typedef struct sapp_icon_desc { + bool sokol_default; + sapp_image_desc images[SAPP_MAX_ICONIMAGES]; +} sapp_icon_desc; + +/* + sapp_allocator + + Used in sapp_desc to provide custom memory-alloc and -free functions + to sokol_app.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sapp_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sapp_allocator; + +/* + sapp_log_item + + Log items are defined via X-Macros and expanded to an enum + 'sapp_log_item', and in debug mode to corresponding + human readable error messages. +*/ +#define _SAPP_LOG_ITEMS \ + _SAPP_LOGITEM_XMACRO(OK, "Ok") \ + _SAPP_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SAPP_LOGITEM_XMACRO(MACOS_INVALID_NSOPENGL_PROFILE, "macos: invalid NSOpenGLProfile (valid choices are 1.0 and 4.1)") \ + _SAPP_LOGITEM_XMACRO(WIN32_LOAD_OPENGL32_DLL_FAILED, "failed loading opengl32.dll") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_HELPER_WINDOW_FAILED, "failed to create helper window") \ + _SAPP_LOGITEM_XMACRO(WIN32_HELPER_WINDOW_GETDC_FAILED, "failed to get helper window DC") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED, "failed to set pixel format for dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_CREATE_DUMMY_CONTEXT_FAILED, "failed to create dummy GL context") \ + _SAPP_LOGITEM_XMACRO(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED, "failed to make dummy GL context current") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED, "failed to get WGL pixel format attribute") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_FIND_PIXELFORMAT_FAILED, "failed to find matching WGL pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED, "failed to get pixel format descriptor") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_SET_PIXELFORMAT_FAILED, "failed to set selected pixel format") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED, "ARB_create_context required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED, "ARB_create_context_profile required") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_VERSION_NOT_SUPPORTED, "requested OpenGL version not supported by GL driver (ERROR_INVALID_VERSION_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED, "requested OpenGL profile not support by GL driver (ERROR_INVALID_PROFILE_ARB)") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT, "CreateContextAttribsARB failed with ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB") \ + _SAPP_LOGITEM_XMACRO(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER, "CreateContextAttribsARB failed for other reason") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED, "D3D11CreateDeviceAndSwapChain() with D3D11_CREATE_DEVICE_DEBUG failed, retrying without debug flag.") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIFACTORY_FAILED, "could not obtain IDXGIFactory object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_GET_IDXGIADAPTER_FAILED, "could not obtain IDXGIAdapter object") \ + _SAPP_LOGITEM_XMACRO(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED, "could not obtain IDXGIDevice1 interface") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK, "RegisterRawInputDevices() failed (on mouse lock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK, "RegisterRawInputDevices() failed (on mouse unlock)") \ + _SAPP_LOGITEM_XMACRO(WIN32_GET_RAW_INPUT_DATA_FAILED, "GetRawInputData() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_LIBGL_FAILED, "failed to load libGL") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED, "failed to load GLX entry points") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_EXTENSION_NOT_FOUND, "GLX extension not found") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_QUERY_VERSION_FAILED, "failed to query GLX version") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_VERSION_TOO_LOW, "GLX version too low (need at least 1.3)") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_GLXFBCONFIGS, "glXGetFBConfigs() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG, "failed to find a suitable GLXFBConfig") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED, "glXGetVisualFromFBConfig failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING, "GLX extensions ARB_create_context and ARB_create_context_profile missing") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_CONTEXT_FAILED, "Failed to create GL context via glXCreateContextAttribsARB") \ + _SAPP_LOGITEM_XMACRO(LINUX_GLX_CREATE_WINDOW_FAILED, "glXCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_CREATE_WINDOW_FAILED, "XCreateWindow() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_API_FAILED, "eglBindAPI(EGL_OPENGL_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_BIND_OPENGL_ES_API_FAILED, "eglBindAPI(EGL_OPENGL_ES_API) failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_DISPLAY_FAILED, "eglGetDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_INITIALIZE_FAILED, "eglInitialize() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_CONFIGS, "eglChooseConfig() returned no configs") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_NO_NATIVE_VISUAL, "eglGetConfigAttrib() for EGL_NATIVE_VISUAL_ID failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_GET_VISUAL_INFO_FAILED, "XGetVisualInfo() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED, "eglCreateWindowSurface() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_CREATE_CONTEXT_FAILED, "eglCreateContext() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_EGL_MAKE_CURRENT_FAILED, "eglMakeCurrent() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD, "X11: Failed to become owner of clipboard selection") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_WRITE_MSG_FAILED, "failed to write message in _sapp_android_msg") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_CREATE, "MSG_CREATE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_RESUME, "MSG_RESUME") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_PAUSE, "MSG_PAUSE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_FOCUS, "MSG_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_NO_FOCUS, "MSG_NO_FOCUS") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_NATIVE_WINDOW, "MSG_SET_NATIVE_WINDOW") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_SET_INPUT_QUEUE, "MSG_SET_INPUT_QUEUE") \ + _SAPP_LOGITEM_XMACRO(ANDROID_MSG_DESTROY, "MSG_DESTROY") \ + _SAPP_LOGITEM_XMACRO(ANDROID_UNKNOWN_MSG, "unknown msg type received") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_STARTED, "loop thread started") \ + _SAPP_LOGITEM_XMACRO(ANDROID_LOOP_THREAD_DONE, "loop thread done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTART, "NativeActivity onStart()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONRESUME, "NativeActivity onResume") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE, "NativeActivity onSaveInstanceState") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED, "NativeActivity onWindowFocusChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONPAUSE, "NativeActivity onPause") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONSTOP, "NativeActivity onStop()") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED, "NativeActivity onNativeWindowCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED, "NativeActivity onNativeWindowDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED, "NativeActivity onInputQueueCreated") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED, "NativeActivity onInputQueueDestroyed") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED, "NativeActivity onConfigurationChanged") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY, "NativeActivity onLowMemory") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONDESTROY, "NativeActivity onDestroy") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_DONE, "NativeActivity done") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_ONCREATE, "NativeActivity onCreate") \ + _SAPP_LOGITEM_XMACRO(ANDROID_CREATE_THREAD_PIPE_FAILED, "failed to create thread pipe") \ + _SAPP_LOGITEM_XMACRO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS, "NativeActivity successfully created") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_SURFACE_FAILED, "wgpu: failed to create surface for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_SWAPCHAIN_FAILED, "wgpu: failed to create swapchain object") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_TEXTURE_FAILED, "wgpu: failed to create depth-stencil texture for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_VIEW_FAILED, "wgpu: failed to create view object for swapchain depth-stencil texture") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_MSAA_TEXTURE_FAILED, "wgpu: failed to create msaa texture for swapchain") \ + _SAPP_LOGITEM_XMACRO(WGPU_SWAPCHAIN_CREATE_MSAA_VIEW_FAILED, "wgpu: failed to create view object for swapchain msaa texture") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_DEVICE_STATUS_ERROR, "wgpu: requesting device failed with status 'error'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_DEVICE_STATUS_UNKNOWN, "wgpu: requesting device failed with status 'unknown'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_ADAPTER_STATUS_UNAVAILABLE, "wgpu: requesting adapter failed with 'unavailable'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_ADAPTER_STATUS_ERROR, "wgpu: requesting adapter failed with status 'error'") \ + _SAPP_LOGITEM_XMACRO(WGPU_REQUEST_ADAPTER_STATUS_UNKNOWN, "wgpu: requesting adapter failed with status 'unknown'") \ + _SAPP_LOGITEM_XMACRO(WGPU_CREATE_INSTANCE_FAILED, "wgpu: failed to create instance") \ + _SAPP_LOGITEM_XMACRO(IMAGE_DATA_SIZE_MISMATCH, "image data size mismatch (must be width*height*4 bytes)") \ + _SAPP_LOGITEM_XMACRO(DROPPED_FILE_PATH_TOO_LONG, "dropped file path too long (sapp_desc.max_dropped_filed_path_length)") \ + _SAPP_LOGITEM_XMACRO(CLIPBOARD_STRING_TOO_BIG, "clipboard string didn't fit into clipboard buffer") \ + +#define _SAPP_LOGITEM_XMACRO(item,msg) SAPP_LOGITEM_##item, +typedef enum sapp_log_item { + _SAPP_LOG_ITEMS +} sapp_log_item; +#undef _SAPP_LOGITEM_XMACRO + +/* + sapp_logger + + Used in sapp_desc to provide a logging function. Please be aware that + without logging function, sokol-app will be completely silent, e.g. it will + not report errors or warnings. For maximum error verbosity, compile in + debug mode (e.g. NDEBUG *not* defined) and install a logger (for instance + the standard logging function from sokol_log.h). +*/ +typedef struct sapp_logger { + void (*func)( + const char* tag, // always "sapp" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SAPP_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_app.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sapp_logger; + +/* + sokol-app initialization options, used as return value of sokol_main() + or sapp_run() argument. +*/ +typedef struct sapp_desc { + void (*init_cb)(void); // these are the user-provided callbacks without user data + void (*frame_cb)(void); + void (*cleanup_cb)(void); + void (*event_cb)(const sapp_event*); + + void* user_data; // these are the user-provided callbacks with user data + void (*init_userdata_cb)(void*); + void (*frame_userdata_cb)(void*); + void (*cleanup_userdata_cb)(void*); + void (*event_userdata_cb)(const sapp_event*, void*); + + int width; // the preferred width of the window / canvas + int height; // the preferred height of the window / canvas + int sample_count; // MSAA sample count + int swap_interval; // the preferred swap interval (ignored on some platforms) + bool high_dpi; // whether the rendering canvas is full-resolution on HighDPI displays + bool fullscreen; // whether the window should be created in fullscreen mode + bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms) + const char* window_title; // the window title as UTF-8 encoded string + bool enable_clipboard; // enable clipboard access, default is false + int clipboard_size; // max size of clipboard content in bytes + bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false + int max_dropped_files; // max number of dropped files to process (default: 1) + int max_dropped_file_path_length; // max length in bytes of a dropped UTF-8 file path (default: 2048) + sapp_icon_desc icon; // the initial window icon to set + sapp_allocator allocator; // optional memory allocation overrides (default: malloc/free) + sapp_logger logger; // logging callback override (default: NO LOGGING!) + + // backend-specific options + int gl_major_version; // override GL/GLES major and minor version (defaults: GL4.1 (macOS) or GL4.3, GLES3.1 (Android) or GLES3.0 + int gl_minor_version; + bool win32_console_utf8; // if true, set the output console codepage to UTF-8 + bool win32_console_create; // if true, attach stdout/stderr to a new console window + bool win32_console_attach; // if true, attach stdout/stderr to parent process + const char* html5_canvas_selector; // css selector of the HTML5 canvas element, default is "#canvas" + bool html5_canvas_resize; // if true, the HTML5 canvas size is set to sapp_desc.width/height, otherwise canvas size is tracked + bool html5_preserve_drawing_buffer; // HTML5 only: whether to preserve default framebuffer content between frames + bool html5_premultiplied_alpha; // HTML5 only: whether the rendered pixels use premultiplied alpha convention + bool html5_ask_leave_site; // initial state of the internal html5_ask_leave_site flag (see sapp_html5_ask_leave_site()) + bool html5_update_document_title; // if true, update the HTML document.title with sapp_desc.window_title + bool html5_bubble_mouse_events; // if true, mouse events will bubble up to the web page + bool html5_bubble_touch_events; // same for touch events + bool html5_bubble_wheel_events; // same for wheel events + bool html5_bubble_key_events; // if true, bubble up *all* key events to browser, not just key events that represent characters + bool html5_bubble_char_events; // if true, bubble up character events to browser + bool html5_use_emsc_set_main_loop; // if true, use emscripten_set_main_loop() instead of emscripten_request_animation_frame_loop() + bool html5_emsc_set_main_loop_simulate_infinite_loop; // this will be passed as the simulate_infinite_loop arg to emscripten_set_main_loop() + bool ios_keyboard_resizes_canvas; // if true, showing the iOS keyboard shrinks the canvas +} sapp_desc; + +/* HTML5 specific: request and response structs for + asynchronously loading dropped-file content. +*/ +typedef enum sapp_html5_fetch_error { + SAPP_HTML5_FETCH_ERROR_NO_ERROR, + SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL, + SAPP_HTML5_FETCH_ERROR_OTHER, +} sapp_html5_fetch_error; + +typedef struct sapp_html5_fetch_response { + bool succeeded; // true if the loading operation has succeeded + sapp_html5_fetch_error error_code; + int file_index; // index of the dropped file (0..sapp_get_num_dropped_filed()-1) + sapp_range data; // pointer and size of the fetched data (data.ptr == buffer.ptr, data.size <= buffer.size) + sapp_range buffer; // the user-provided buffer ptr/size pair (buffer.ptr == data.ptr, buffer.size >= data.size) + void* user_data; // user-provided user data pointer +} sapp_html5_fetch_response; + +typedef struct sapp_html5_fetch_request { + int dropped_file_index; // 0..sapp_get_num_dropped_files()-1 + void (*callback)(const sapp_html5_fetch_response*); // response callback function pointer (required) + sapp_range buffer; // ptr/size of a memory buffer to load the data into + void* user_data; // optional userdata pointer +} sapp_html5_fetch_request; + +/* + sapp_mouse_cursor + + Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) +*/ +typedef enum sapp_mouse_cursor { + SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor + SAPP_MOUSECURSOR_ARROW, + SAPP_MOUSECURSOR_IBEAM, + SAPP_MOUSECURSOR_CROSSHAIR, + SAPP_MOUSECURSOR_POINTING_HAND, + SAPP_MOUSECURSOR_RESIZE_EW, + SAPP_MOUSECURSOR_RESIZE_NS, + SAPP_MOUSECURSOR_RESIZE_NWSE, + SAPP_MOUSECURSOR_RESIZE_NESW, + SAPP_MOUSECURSOR_RESIZE_ALL, + SAPP_MOUSECURSOR_NOT_ALLOWED, + _SAPP_MOUSECURSOR_NUM, +} sapp_mouse_cursor; + +/* user-provided functions */ +extern sapp_desc sokol_main(int argc, char* argv[]); + +/* returns true after sokol-app has been initialized */ +SOKOL_APP_API_DECL bool sapp_isvalid(void); +/* returns the current framebuffer width in pixels */ +SOKOL_APP_API_DECL int sapp_width(void); +/* same as sapp_width(), but returns float */ +SOKOL_APP_API_DECL float sapp_widthf(void); +/* returns the current framebuffer height in pixels */ +SOKOL_APP_API_DECL int sapp_height(void); +/* same as sapp_height(), but returns float */ +SOKOL_APP_API_DECL float sapp_heightf(void); +/* get default framebuffer color pixel format */ +SOKOL_APP_API_DECL int sapp_color_format(void); +/* get default framebuffer depth pixel format */ +SOKOL_APP_API_DECL int sapp_depth_format(void); +/* get default framebuffer sample count */ +SOKOL_APP_API_DECL int sapp_sample_count(void); +/* returns true when high_dpi was requested and actually running in a high-dpi scenario */ +SOKOL_APP_API_DECL bool sapp_high_dpi(void); +/* returns the dpi scaling factor (window pixels to framebuffer pixels) */ +SOKOL_APP_API_DECL float sapp_dpi_scale(void); +/* show or hide the mobile device onscreen keyboard */ +SOKOL_APP_API_DECL void sapp_show_keyboard(bool show); +/* return true if the mobile device onscreen keyboard is currently shown */ +SOKOL_APP_API_DECL bool sapp_keyboard_shown(void); +/* query fullscreen mode */ +SOKOL_APP_API_DECL bool sapp_is_fullscreen(void); +/* toggle fullscreen mode */ +SOKOL_APP_API_DECL void sapp_toggle_fullscreen(void); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL void sapp_show_mouse(bool show); +/* show or hide the mouse cursor */ +SOKOL_APP_API_DECL bool sapp_mouse_shown(void); +/* enable/disable mouse-pointer-lock mode */ +SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); +/* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ +SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* set mouse cursor type */ +SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); +/* get current mouse cursor type */ +SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); +/* return the userdata pointer optionally provided in sapp_desc */ +SOKOL_APP_API_DECL void* sapp_userdata(void); +/* return a copy of the sapp_desc structure */ +SOKOL_APP_API_DECL sapp_desc sapp_query_desc(void); +/* initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_request_quit(void); +/* cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received) */ +SOKOL_APP_API_DECL void sapp_cancel_quit(void); +/* initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUESTED) */ +SOKOL_APP_API_DECL void sapp_quit(void); +/* call from inside event callback to consume the current event (don't forward to platform) */ +SOKOL_APP_API_DECL void sapp_consume_event(void); +/* get the current frame counter (for comparison with sapp_event.frame_count) */ +SOKOL_APP_API_DECL uint64_t sapp_frame_count(void); +/* get an averaged/smoothed frame duration in seconds */ +SOKOL_APP_API_DECL double sapp_frame_duration(void); +/* write string into clipboard */ +SOKOL_APP_API_DECL void sapp_set_clipboard_string(const char* str); +/* read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED) */ +SOKOL_APP_API_DECL const char* sapp_get_clipboard_string(void); +/* set the window title (only on desktop platforms) */ +SOKOL_APP_API_DECL void sapp_set_window_title(const char* str); +/* set the window icon (only on Windows and Linux) */ +SOKOL_APP_API_DECL void sapp_set_icon(const sapp_icon_desc* icon_desc); +/* gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event) */ +SOKOL_APP_API_DECL int sapp_get_num_dropped_files(void); +/* gets the dropped file paths */ +SOKOL_APP_API_DECL const char* sapp_get_dropped_file_path(int index); + +/* special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub) */ +SOKOL_APP_API_DECL void sapp_run(const sapp_desc* desc); + +/* EGL: get EGLDisplay object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_display(void); +/* EGL: get EGLContext object */ +SOKOL_APP_API_DECL const void* sapp_egl_get_context(void); + +/* HTML5: enable or disable the hardwired "Leave Site?" dialog box */ +SOKOL_APP_API_DECL void sapp_html5_ask_leave_site(bool ask); +/* HTML5: get byte size of a dropped file */ +SOKOL_APP_API_DECL uint32_t sapp_html5_get_dropped_file_size(int index); +/* HTML5: asynchronously load the content of a dropped file */ +SOKOL_APP_API_DECL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request); + +/* Metal: get bridged pointer to Metal device object */ +SOKOL_APP_API_DECL const void* sapp_metal_get_device(void); +/* Metal: get bridged pointer to MTKView's current drawable of type CAMetalDrawable */ +SOKOL_APP_API_DECL const void* sapp_metal_get_current_drawable(void); +/* Metal: get bridged pointer to MTKView's depth-stencil texture of type MTLTexture */ +SOKOL_APP_API_DECL const void* sapp_metal_get_depth_stencil_texture(void); +/* Metal: get bridged pointer to MTKView's msaa-color-texture of type MTLTexture (may be null) */ +SOKOL_APP_API_DECL const void* sapp_metal_get_msaa_color_texture(void); +/* macOS: get bridged pointer to macOS NSWindow */ +SOKOL_APP_API_DECL const void* sapp_macos_get_window(void); +/* iOS: get bridged pointer to iOS UIWindow */ +SOKOL_APP_API_DECL const void* sapp_ios_get_window(void); + +/* D3D11: get pointer to ID3D11Device object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_device(void); +/* D3D11: get pointer to ID3D11DeviceContext object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_device_context(void); +/* D3D11: get pointer to IDXGISwapChain object */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_swap_chain(void); +/* D3D11: get pointer to ID3D11RenderTargetView object for rendering */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_render_view(void); +/* D3D11: get pointer ID3D11RenderTargetView object for msaa-resolve (may return null) */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_resolve_view(void); +/* D3D11: get pointer ID3D11DepthStencilView */ +SOKOL_APP_API_DECL const void* sapp_d3d11_get_depth_stencil_view(void); +/* Win32: get the HWND window handle */ +SOKOL_APP_API_DECL const void* sapp_win32_get_hwnd(void); + +/* WebGPU: get WGPUDevice handle */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_device(void); +/* WebGPU: get swapchain's WGPUTextureView handle for rendering */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_render_view(void); +/* WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null) */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_resolve_view(void); +/* WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface */ +SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void); + +/* GL: get framebuffer object */ +SOKOL_APP_API_DECL uint32_t sapp_gl_get_framebuffer(void); +/* GL: get major version */ +SOKOL_APP_API_DECL int sapp_gl_get_major_version(void); +/* GL: get minor version */ +SOKOL_APP_API_DECL int sapp_gl_get_minor_version(void); +/* GL: return true if the context is GLES */ +SOKOL_APP_API_DECL bool sapp_gl_is_gles(void); + +/* X11: get Window */ +SOKOL_APP_API_DECL const void* sapp_x11_get_window(void); +/* X11: get Display */ +SOKOL_APP_API_DECL const void* sapp_x11_get_display(void); + +/* Android: get native activity handle */ +SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } + +#endif + +#endif // SOKOL_APP_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_APP_IMPL +#define SOKOL_APP_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sapp_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free +#include // memset, strncmp +#include // size_t +#include // roundf + +// helper macros +#define _sapp_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sapp_absf(a) (((a)<0.0f)?-(a):(a)) + +#define _SAPP_MAX_TITLE_LENGTH (128) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH (640) +#define _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT (480) +// NOTE: the pixel format values *must* be compatible with sg_pixel_format +#define _SAPP_PIXELFORMAT_RGBA8 (23) +#define _SAPP_PIXELFORMAT_BGRA8 (28) +#define _SAPP_PIXELFORMAT_DEPTH (43) +#define _SAPP_PIXELFORMAT_DEPTH_STENCIL (44) + +// check if the config defines are alright +#if defined(__APPLE__) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_app.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #define _SAPP_APPLE (1) + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + /* MacOS */ + #define _SAPP_MACOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE) + #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE") + #endif + #else + /* iOS or iOS Simulator */ + #define _SAPP_IOS (1) + #if !defined(SOKOL_METAL) && !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for iOS, must be SOKOL_METAL or SOKOL_GLES3") + #endif + #endif +#elif defined(__EMSCRIPTEN__) + /* emscripten (asm.js or wasm) */ + #define _SAPP_EMSCRIPTEN (1) + #if !defined(SOKOL_GLES3) && !defined(SOKOL_WGPU) + #error("sokol_app.h: unknown 3D API selected for emscripten, must be SOKOL_GLES3 or SOKOL_WGPU") + #endif +#elif defined(_WIN32) + /* Windows (D3D11 or GL) */ + #define _SAPP_WIN32 (1) + #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE) && !defined(SOKOL_NOAPI) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11, SOKOL_GLCORE or SOKOL_NOAPI") + #endif +#elif defined(__ANDROID__) + /* Android */ + #define _SAPP_ANDROID (1) + #if !defined(SOKOL_GLES3) + #error("sokol_app.h: unknown 3D API selected for Android, must be SOKOL_GLES3") + #endif + #if defined(SOKOL_NO_ENTRY) + #error("sokol_app.h: SOKOL_NO_ENTRY is not supported on Android") + #endif +#elif defined(__linux__) || defined(__unix__) + /* Linux */ + #define _SAPP_LINUX (1) + #if defined(SOKOL_GLCORE) + #if !defined(SOKOL_FORCE_EGL) + #define _SAPP_GLX (1) + #endif + #define GL_GLEXT_PROTOTYPES + #include + #elif defined(SOKOL_GLES3) + #include + #include + #else + #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE, SOKOL_GLES3") + #endif +#else +#error "sokol_app.h: Unknown platform" +#endif + +#if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) + #define _SAPP_ANY_GL (1) +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(_SAPP_APPLE) + #if defined(SOKOL_METAL) + #import + #import + #endif + #if defined(_SAPP_MACOS) + #if defined(_SAPP_ANY_GL) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #include + #include + #endif + #elif defined(_SAPP_IOS) + #import + #if defined(_SAPP_ANY_GL) + #import + #include + #endif + #endif + #include + #include +#elif defined(_SAPP_EMSCRIPTEN) + #if defined(SOKOL_WGPU) + #include + #endif + #if defined(SOKOL_GLES3) + #include + #endif + #include + #include +#elif defined(_SAPP_WIN32) + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ + #pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */ + #pragma warning(disable:4054) /* 'type cast': from function pointer */ + #pragma warning(disable:4055) /* 'type cast': from data pointer */ + #pragma warning(disable:4505) /* unreferenced local function has been removed */ + #pragma warning(disable:4115) /* /W4: 'ID3D11ModuleInstance': named type definition in parentheses (in d3d11.h) */ + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + #if !defined(SOKOL_NO_ENTRY) // if SOKOL_NO_ENTRY is defined, it's the application's responsibility to use the right subsystem + + #if defined(SOKOL_WIN32_FORCE_MAIN) && defined(SOKOL_WIN32_FORCE_WINMAIN) + // If both are defined, it's the application's responsibility to use the right subsystem + #elif defined(SOKOL_WIN32_FORCE_MAIN) + #pragma comment (linker, "/subsystem:console") + #else + #pragma comment (linker, "/subsystem:windows") + #endif + #endif + #include /* freopen_s() */ + #include /* wcslen() */ + + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "shell32") /* CommandLineToArgvW, DragQueryFileW, DragFinished */ + #pragma comment (lib, "gdi32") + #if defined(SOKOL_D3D11) + #pragma comment (lib, "dxgi") + #pragma comment (lib, "d3d11") + #endif + + #if defined(SOKOL_D3D11) + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #include + #include + // DXGI_SWAP_EFFECT_FLIP_DISCARD is only defined in newer Windows SDKs, so don't depend on it + #define _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD (4) + #endif + #ifndef WM_MOUSEHWHEEL /* see https://github.com/floooh/sokol/issues/138 */ + #define WM_MOUSEHWHEEL (0x020E) + #endif + #ifndef WM_DPICHANGED + #define WM_DPICHANGED (0x02E0) + #endif +#elif defined(_SAPP_ANDROID) + #include + #include + #include + #include + #include + #include + #include +#elif defined(_SAPP_LINUX) + #define GL_GLEXT_PROTOTYPES + #include + #include + #include + #include + #include + #include + #include + #include + #include /* XC_* font cursors */ + #include /* CARD32 */ + #if !defined(_SAPP_GLX) + #include + #endif + #include /* dlopen, dlsym, dlclose */ + #include /* LONG_MAX */ + #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ + #include + #include +#endif + +#if defined(_SAPP_APPLE) + // this is ARC compatible + #if defined(__cplusplus) + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = type(); } + #else + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SAPP_CLEAR_ARC_STRUCT(type, item) { _sapp_clear(&item, sizeof(item)); } +#endif + + +// ███████ ██████ █████ ███ ███ ███████ ████████ ██ ███ ███ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ████ ████ ██ ████ ██ ██ +// █████ ██████ ███████ ██ ████ ██ █████ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██████ +// +// >>frame timing +#define _SAPP_RING_NUM_SLOTS (256) +typedef struct { + int head; + int tail; + double buf[_SAPP_RING_NUM_SLOTS]; +} _sapp_ring_t; + +_SOKOL_PRIVATE int _sapp_ring_idx(int i) { + return i % _SAPP_RING_NUM_SLOTS; +} + +_SOKOL_PRIVATE void _sapp_ring_init(_sapp_ring_t* ring) { + ring->head = 0; + ring->tail = 0; +} + +_SOKOL_PRIVATE bool _sapp_ring_full(_sapp_ring_t* ring) { + return _sapp_ring_idx(ring->head + 1) == ring->tail; +} + +_SOKOL_PRIVATE bool _sapp_ring_empty(_sapp_ring_t* ring) { + return ring->head == ring->tail; +} + +_SOKOL_PRIVATE int _sapp_ring_count(_sapp_ring_t* ring) { + int count; + if (ring->head >= ring->tail) { + count = ring->head - ring->tail; + } + else { + count = (ring->head + _SAPP_RING_NUM_SLOTS) - ring->tail; + } + SOKOL_ASSERT((count >= 0) && (count < _SAPP_RING_NUM_SLOTS)); + return count; +} + +_SOKOL_PRIVATE void _sapp_ring_enqueue(_sapp_ring_t* ring, double val) { + SOKOL_ASSERT(!_sapp_ring_full(ring)); + ring->buf[ring->head] = val; + ring->head = _sapp_ring_idx(ring->head + 1); +} + +_SOKOL_PRIVATE double _sapp_ring_dequeue(_sapp_ring_t* ring) { + SOKOL_ASSERT(!_sapp_ring_empty(ring)); + double val = ring->buf[ring->tail]; + ring->tail = _sapp_ring_idx(ring->tail + 1); + return val; +} + +/* + NOTE: + + Q: Why not use CAMetalDrawable.presentedTime on macOS and iOS? + A: The value appears to be highly unstable during the first few + seconds, sometimes several frames are dropped in sequence, or + switch between 120 and 60 Hz for a few frames. Simply measuring + and averaging the frame time yielded a more stable frame duration. + Maybe switching to CVDisplayLink would yield better results. + Until then just measure the time. +*/ +typedef struct { + #if defined(_SAPP_APPLE) + struct { + mach_timebase_info_data_t timebase; + uint64_t start; + } mach; + #elif defined(_SAPP_EMSCRIPTEN) + // empty + #elif defined(_SAPP_WIN32) + struct { + LARGE_INTEGER freq; + LARGE_INTEGER start; + } win; + #else // Linux, Android, ... + #ifdef CLOCK_MONOTONIC + #define _SAPP_CLOCK_MONOTONIC CLOCK_MONOTONIC + #else + // on some embedded platforms, CLOCK_MONOTONIC isn't defined + #define _SAPP_CLOCK_MONOTONIC (1) + #endif + struct { + uint64_t start; + } posix; + #endif +} _sapp_timestamp_t; + +_SOKOL_PRIVATE int64_t _sapp_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} + +_SOKOL_PRIVATE void _sapp_timestamp_init(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + mach_timebase_info(&ts->mach.timebase); + ts->mach.start = mach_absolute_time(); + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + #elif defined(_SAPP_WIN32) + QueryPerformanceFrequency(&ts->win.freq); + QueryPerformanceCounter(&ts->win.start); + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + ts->posix.start = (uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec; + #endif +} + +_SOKOL_PRIVATE double _sapp_timestamp_now(_sapp_timestamp_t* ts) { + #if defined(_SAPP_APPLE) + const uint64_t traw = mach_absolute_time() - ts->mach.start; + const uint64_t now = (uint64_t) _sapp_int64_muldiv((int64_t)traw, (int64_t)ts->mach.timebase.numer, (int64_t)ts->mach.timebase.denom); + return (double)now / 1000000000.0; + #elif defined(_SAPP_EMSCRIPTEN) + (void)ts; + SOKOL_ASSERT(false); + return 0.0; + #elif defined(_SAPP_WIN32) + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - ts->win.start.QuadPart, 1000000000, ts->win.freq.QuadPart); + return (double)now / 1000000000.0; + #else + struct timespec tspec; + clock_gettime(_SAPP_CLOCK_MONOTONIC, &tspec); + const uint64_t now = ((uint64_t)tspec.tv_sec*1000000000 + (uint64_t)tspec.tv_nsec) - ts->posix.start; + return (double)now / 1000000000.0; + #endif +} + +typedef struct { + double last; + double accum; + double avg; + int spike_count; + int num; + _sapp_timestamp_t timestamp; + _sapp_ring_t ring; +} _sapp_timing_t; + +_SOKOL_PRIVATE void _sapp_timing_reset(_sapp_timing_t* t) { + t->last = 0.0; + t->accum = 0.0; + t->spike_count = 0; + t->num = 0; + _sapp_ring_init(&t->ring); +} + +_SOKOL_PRIVATE void _sapp_timing_init(_sapp_timing_t* t) { + t->avg = 1.0 / 60.0; // dummy value until first actual value is available + _sapp_timing_reset(t); + _sapp_timestamp_init(&t->timestamp); +} + +_SOKOL_PRIVATE void _sapp_timing_put(_sapp_timing_t* t, double dur) { + // arbitrary upper limit to ignore outliers (e.g. during window resizing, or debugging) + double min_dur = 0.0; + double max_dur = 0.1; + // if we have enough samples for a useful average, use a much tighter 'valid window' + if (_sapp_ring_full(&t->ring)) { + min_dur = t->avg * 0.8; + max_dur = t->avg * 1.2; + } + if ((dur < min_dur) || (dur > max_dur)) { + t->spike_count++; + // if there have been many spikes in a row, the display refresh rate + // might have changed, so a timing reset is needed + if (t->spike_count > 20) { + _sapp_timing_reset(t); + } + return; + } + if (_sapp_ring_full(&t->ring)) { + double old_val = _sapp_ring_dequeue(&t->ring); + t->accum -= old_val; + t->num -= 1; + } + _sapp_ring_enqueue(&t->ring, dur); + t->accum += dur; + t->num += 1; + SOKOL_ASSERT(t->num > 0); + t->avg = t->accum / t->num; + t->spike_count = 0; +} + +_SOKOL_PRIVATE void _sapp_timing_discontinuity(_sapp_timing_t* t) { + t->last = 0.0; +} + +_SOKOL_PRIVATE void _sapp_timing_measure(_sapp_timing_t* t) { + const double now = _sapp_timestamp_now(&t->timestamp); + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE void _sapp_timing_external(_sapp_timing_t* t, double now) { + if (t->last > 0.0) { + double dur = now - t->last; + _sapp_timing_put(t, dur); + } + t->last = now; +} + +_SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) { + return t->avg; +} + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >> structs +#if defined(_SAPP_MACOS) +@interface _sapp_macos_app_delegate : NSObject +@end +@interface _sapp_macos_window : NSWindow +@end +@interface _sapp_macos_window_delegate : NSObject +@end +#if defined(SOKOL_METAL) + @interface _sapp_macos_view : MTKView + @end +#elif defined(SOKOL_GLCORE) + @interface _sapp_macos_view : NSOpenGLView + - (void)timerFired:(id)sender; + @end +#endif // SOKOL_GLCORE + +typedef struct { + uint32_t flags_changed_store; + uint8_t mouse_buttons; + NSWindow* window; + NSTrackingArea* tracking_area; + id keyup_monitor; + _sapp_macos_app_delegate* app_dlg; + _sapp_macos_window_delegate* win_dlg; + _sapp_macos_view* view; + NSCursor* cursors[_SAPP_MOUSECURSOR_NUM]; + #if defined(SOKOL_METAL) + id mtl_device; + #endif +} _sapp_macos_t; + +#endif // _SAPP_MACOS + +#if defined(_SAPP_IOS) + +@interface _sapp_app_delegate : NSObject +@end +@interface _sapp_textfield_dlg : NSObject +- (void)keyboardWasShown:(NSNotification*)notif; +- (void)keyboardWillBeHidden:(NSNotification*)notif; +- (void)keyboardDidChangeFrame:(NSNotification*)notif; +@end +#if defined(SOKOL_METAL) + @interface _sapp_ios_view : MTKView; + @end +#else + @interface _sapp_ios_view : GLKView + @end +#endif + +typedef struct { + UIWindow* window; + _sapp_ios_view* view; + UITextField* textfield; + _sapp_textfield_dlg* textfield_dlg; + #if defined(SOKOL_METAL) + UIViewController* view_ctrl; + id mtl_device; + #else + GLKViewController* view_ctrl; + EAGLContext* eagl_ctx; + #endif + bool suspended; +} _sapp_ios_t; + +#endif // _SAPP_IOS + +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(SOKOL_WGPU) +typedef struct { + WGPUInstance instance; + WGPUAdapter adapter; + WGPUDevice device; + WGPUTextureFormat render_format; + WGPUSurface surface; + WGPUSwapChain swapchain; + WGPUTexture msaa_tex; + WGPUTextureView msaa_view; + WGPUTexture depth_stencil_tex; + WGPUTextureView depth_stencil_view; + WGPUTextureView swapchain_view; + bool async_init_done; +} _sapp_wgpu_t; +#endif + +typedef struct { + bool mouse_lock_requested; + uint16_t mouse_buttons; +} _sapp_emsc_t; +#endif // _SAPP_EMSCRIPTEN + +#if defined(SOKOL_D3D11) && defined(_SAPP_WIN32) +typedef struct { + ID3D11Device* device; + ID3D11DeviceContext* device_context; + ID3D11Texture2D* rt; + ID3D11RenderTargetView* rtv; + ID3D11Texture2D* msaa_rt; + ID3D11RenderTargetView* msaa_rtv; + ID3D11Texture2D* ds; + ID3D11DepthStencilView* dsv; + DXGI_SWAP_CHAIN_DESC swap_chain_desc; + IDXGISwapChain* swap_chain; + IDXGIDevice1* dxgi_device; + bool use_dxgi_frame_stats; + UINT sync_refresh_count; +} _sapp_d3d11_t; +#endif + +#if defined(_SAPP_WIN32) + +#ifndef DPI_ENUMS_DECLARED +typedef enum PROCESS_DPI_AWARENESS +{ + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; +typedef enum MONITOR_DPI_TYPE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; +#endif // DPI_ENUMS_DECLARED + +typedef struct { + bool aware; + float content_scale; + float window_scale; + float mouse_scale; +} _sapp_win32_dpi_t; + +typedef struct { + HWND hwnd; + HMONITOR hmonitor; + HDC dc; + HICON big_icon; + HICON small_icon; + HCURSOR cursors[_SAPP_MOUSECURSOR_NUM]; + UINT orig_codepage; + RECT stored_window_rect; // used to restore window pos/size when toggling fullscreen => windowed + bool is_win10_or_greater; + bool in_create_window; + bool iconified; + _sapp_win32_dpi_t dpi; + struct { + struct { + LONG pos_x, pos_y; + bool pos_valid; + } lock; + struct { + LONG pos_x, pos_y; + bool pos_valid; + } raw_input; + bool requested_lock; + bool tracked; + uint8_t capture_mask; + } mouse; + uint8_t raw_input_data[256]; +} _sapp_win32_t; + +#if defined(SOKOL_GLCORE) +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void); +typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC); +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC,HGLRC,const int*); +typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC); +typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); +typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); +typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); +typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); + +typedef struct { + HINSTANCE opengl32; + HGLRC gl_ctx; + PFN_wglCreateContext CreateContext; + PFN_wglDeleteContext DeleteContext; + PFN_wglGetProcAddress GetProcAddress; + PFN_wglGetCurrentDC GetCurrentDC; + PFN_wglMakeCurrent MakeCurrent; + PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; + PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; + PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; + PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + // special case glGetIntegerv + void (WINAPI *GetIntegerv)(uint32_t pname, int32_t* data); + bool ext_swap_control; + bool arb_multisample; + bool arb_pixel_format; + bool arb_create_context; + bool arb_create_context_profile; + HWND msg_hwnd; + HDC msg_dc; +} _sapp_wgl_t; +#endif // SOKOL_GLCORE + +#endif // _SAPP_WIN32 + +#if defined(_SAPP_ANDROID) +typedef enum { + _SOKOL_ANDROID_MSG_CREATE, + _SOKOL_ANDROID_MSG_RESUME, + _SOKOL_ANDROID_MSG_PAUSE, + _SOKOL_ANDROID_MSG_FOCUS, + _SOKOL_ANDROID_MSG_NO_FOCUS, + _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW, + _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE, + _SOKOL_ANDROID_MSG_DESTROY, +} _sapp_android_msg_t; + +typedef struct { + pthread_t thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + int read_from_main_fd; + int write_from_main_fd; +} _sapp_android_pt_t; + +typedef struct { + ANativeWindow* window; + AInputQueue* input; +} _sapp_android_resources_t; + +typedef struct { + ANativeActivity* activity; + _sapp_android_pt_t pt; + _sapp_android_resources_t pending; + _sapp_android_resources_t current; + ALooper* looper; + bool is_thread_started; + bool is_thread_stopping; + bool is_thread_stopped; + bool has_created; + bool has_resumed; + bool has_focus; + EGLConfig config; + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_android_t; + +#endif // _SAPP_ANDROID + +#if defined(_SAPP_LINUX) + +#define _SAPP_X11_XDND_VERSION (5) +#define _SAPP_X11_MAX_X11_KEYCODES (256) + +#define GLX_VENDOR 1 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_DOUBLEBUFFER 5 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_SAMPLES 0x186a1 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 + +typedef XID GLXWindow; +typedef XID GLXDrawable; +typedef struct __GLXFBConfig* GLXFBConfig; +typedef struct __GLXcontext* GLXContext; +typedef void (*__GLXextproc)(void); + +typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); +typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); +typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); +typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); +typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); +typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); +typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); +typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); +typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); +typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const char *procName); +typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); +typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); +typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); +typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); + +typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); + +typedef struct { + bool available; + int major_opcode; + int event_base; + int error_base; + int major; + int minor; +} _sapp_xi_t; + +typedef struct { + int version; + Window source; + Atom format; + Atom XdndAware; + Atom XdndEnter; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndFinished; + Atom XdndSelection; + Atom XdndTypeList; + Atom text_uri_list; +} _sapp_xdnd_t; + +typedef struct { + uint8_t mouse_buttons; + Display* display; + int screen; + Window root; + Colormap colormap; + Window window; + Cursor hidden_cursor; + Cursor cursors[_SAPP_MOUSECURSOR_NUM]; + int window_state; + float dpi; + unsigned char error_code; + Atom UTF8_STRING; + Atom CLIPBOARD; + Atom TARGETS; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + Atom WM_STATE; + Atom NET_WM_NAME; + Atom NET_WM_ICON_NAME; + Atom NET_WM_ICON; + Atom NET_WM_STATE; + Atom NET_WM_STATE_FULLSCREEN; + _sapp_xi_t xi; + _sapp_xdnd_t xdnd; + // XLib manual says keycodes are in the range [8, 255] inclusive. + // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html + bool key_repeat[_SAPP_X11_MAX_X11_KEYCODES]; +} _sapp_x11_t; + +#if defined(_SAPP_GLX) + +typedef struct { + void* libgl; + int major; + int minor; + int event_base; + int error_base; + GLXContext ctx; + GLXWindow window; + + // GLX 1.3 functions + PFNGLXGETFBCONFIGSPROC GetFBConfigs; + PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; + PFNGLXGETCLIENTSTRINGPROC GetClientString; + PFNGLXQUERYEXTENSIONPROC QueryExtension; + PFNGLXQUERYVERSIONPROC QueryVersion; + PFNGLXDESTROYCONTEXTPROC DestroyContext; + PFNGLXMAKECURRENTPROC MakeCurrent; + PFNGLXSWAPBUFFERSPROC SwapBuffers; + PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; + PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; + PFNGLXCREATEWINDOWPROC CreateWindow; + PFNGLXDESTROYWINDOWPROC DestroyWindow; + + // GLX 1.4 and extension functions + PFNGLXGETPROCADDRESSPROC GetProcAddress; + PFNGLXGETPROCADDRESSPROC GetProcAddressARB; + PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; + PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; + PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; + + // special case glGetIntegerv + void (*GetIntegerv)(uint32_t pname, int32_t* data); + + // extension availability + bool EXT_swap_control; + bool MESA_swap_control; + bool ARB_multisample; + bool ARB_create_context; + bool ARB_create_context_profile; +} _sapp_glx_t; + +#else + +typedef struct { + EGLDisplay display; + EGLContext context; + EGLSurface surface; +} _sapp_egl_t; + +#endif // _SAPP_GLX +#endif // _SAPP_LINUX + +#if defined(_SAPP_ANY_GL) +typedef struct { + uint32_t framebuffer; +} _sapp_gl_t; +#endif + +typedef struct { + bool enabled; + int buf_size; + char* buffer; +} _sapp_clipboard_t; + +typedef struct { + bool enabled; + int max_files; + int max_path_length; + int num_files; + int buf_size; + char* buffer; +} _sapp_drop_t; + +typedef struct { + float x, y; + float dx, dy; + bool shown; + bool locked; + bool pos_valid; + sapp_mouse_cursor current_cursor; +} _sapp_mouse_t; + +typedef struct { + sapp_desc desc; + bool valid; + bool fullscreen; + bool first_frame; + bool init_called; + bool cleanup_called; + bool quit_requested; + bool quit_ordered; + bool event_consumed; + bool html5_ask_leave_site; + bool onscreen_keyboard_shown; + int window_width; + int window_height; + int framebuffer_width; + int framebuffer_height; + int sample_count; + int swap_interval; + float dpi_scale; + uint64_t frame_count; + _sapp_timing_t timing; + sapp_event event; + _sapp_mouse_t mouse; + _sapp_clipboard_t clipboard; + _sapp_drop_t drop; + sapp_icon_desc default_icon_desc; + uint32_t* default_icon_pixels; + #if defined(_SAPP_MACOS) + _sapp_macos_t macos; + #elif defined(_SAPP_IOS) + _sapp_ios_t ios; + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_t emsc; + #if defined(SOKOL_WGPU) + _sapp_wgpu_t wgpu; + #endif + #elif defined(_SAPP_WIN32) + _sapp_win32_t win32; + #if defined(SOKOL_D3D11) + _sapp_d3d11_t d3d11; + #elif defined(SOKOL_GLCORE) + _sapp_wgl_t wgl; + #endif + #elif defined(_SAPP_ANDROID) + _sapp_android_t android; + #elif defined(_SAPP_LINUX) + _sapp_x11_t x11; + #if defined(_SAPP_GLX) + _sapp_glx_t glx; + #else + _sapp_egl_t egl; + #endif + #endif + #if defined(_SAPP_ANY_GL) + _sapp_gl_t gl; + #endif + char html5_canvas_selector[_SAPP_MAX_TITLE_LENGTH]; + char window_title[_SAPP_MAX_TITLE_LENGTH]; // UTF-8 + wchar_t window_title_wide[_SAPP_MAX_TITLE_LENGTH]; // UTF-32 or UCS-2 */ + sapp_keycode keycodes[SAPP_MAX_KEYCODES]; +} _sapp_t; +static _sapp_t _sapp; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SAPP_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sapp_log_messages[] = { + _SAPP_LOG_ITEMS +}; +#undef _SAPP_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SAPP_PANIC(code) _sapp_log(SAPP_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SAPP_ERROR(code) _sapp_log(SAPP_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SAPP_WARN(code) _sapp_log(SAPP_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SAPP_INFO(code) _sapp_log(SAPP_LOGITEM_ ##code, 3, 0, __LINE__) + +static void _sapp_log(sapp_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sapp.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sapp_log_messages[log_item]; + } + #endif + _sapp.desc.logger.func("sapp", log_level, (uint32_t)log_item, msg, line_nr, filename, _sapp.desc.logger.user_data); + } + else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _sapp_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sapp_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sapp.desc.allocator.alloc_fn) { + ptr = _sapp.desc.allocator.alloc_fn(size, _sapp.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SAPP_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sapp_malloc_clear(size_t size) { + void* ptr = _sapp_malloc(size); + _sapp_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sapp_free(void* ptr) { + if (_sapp.desc.allocator.free_fn) { + _sapp.desc.allocator.free_fn(ptr, _sapp.desc.allocator.user_data); + } + else { + free(ptr); + } +} + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers +_SOKOL_PRIVATE void _sapp_call_init(void) { + if (_sapp.desc.init_cb) { + _sapp.desc.init_cb(); + } + else if (_sapp.desc.init_userdata_cb) { + _sapp.desc.init_userdata_cb(_sapp.desc.user_data); + } + _sapp.init_called = true; +} + +_SOKOL_PRIVATE void _sapp_call_frame(void) { + if (_sapp.init_called && !_sapp.cleanup_called) { + if (_sapp.desc.frame_cb) { + _sapp.desc.frame_cb(); + } + else if (_sapp.desc.frame_userdata_cb) { + _sapp.desc.frame_userdata_cb(_sapp.desc.user_data); + } + } +} + +_SOKOL_PRIVATE void _sapp_call_cleanup(void) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.cleanup_cb) { + _sapp.desc.cleanup_cb(); + } + else if (_sapp.desc.cleanup_userdata_cb) { + _sapp.desc.cleanup_userdata_cb(_sapp.desc.user_data); + } + _sapp.cleanup_called = true; + } +} + +_SOKOL_PRIVATE bool _sapp_call_event(const sapp_event* e) { + if (!_sapp.cleanup_called) { + if (_sapp.desc.event_cb) { + _sapp.desc.event_cb(e); + } + else if (_sapp.desc.event_userdata_cb) { + _sapp.desc.event_userdata_cb(e, _sapp.desc.user_data); + } + } + if (_sapp.event_consumed) { + _sapp.event_consumed = false; + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE char* _sapp_dropped_file_path_ptr(int index) { + SOKOL_ASSERT(_sapp.drop.buffer); + SOKOL_ASSERT((index >= 0) && (index <= _sapp.drop.max_files)); + int offset = index * _sapp.drop.max_path_length; + SOKOL_ASSERT(offset < _sapp.drop.buf_size); + return &_sapp.drop.buffer[offset]; +} + +/* Copy a string into a fixed size buffer with guaranteed zero- + termination. + + Return false if the string didn't fit into the buffer and had to be clamped. + + FIXME: Currently UTF-8 strings might become invalid if the string + is clamped, because the last zero-byte might be written into + the middle of a multi-byte sequence. +*/ +_SOKOL_PRIVATE bool _sapp_strcpy(const char* src, char* dst, int max_len) { + SOKOL_ASSERT(src && dst && (max_len > 0)); + char* const end = &(dst[max_len-1]); + char c = 0; + for (int i = 0; i < max_len; i++) { + c = *src; + if (c != 0) { + src++; + } + *dst++ = c; + } + /* truncated? */ + if (c != 0) { + *end = 0; + return false; + } + else { + return true; + } +} + +_SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sapp_desc res = *desc; + res.sample_count = _sapp_def(res.sample_count, 1); + res.swap_interval = _sapp_def(res.swap_interval, 1); + if (0 == res.gl_major_version) { + #if defined(SOKOL_GLCORE) + res.gl_major_version = 4; + #if defined(_SAPP_APPLE) + res.gl_minor_version = 1; + #else + res.gl_minor_version = 3; + #endif + #elif defined(SOKOL_GLES3) + res.gl_major_version = 3; + #if defined(_SAPP_ANDROID) || defined(_SAPP_LINUX) + res.gl_minor_version = 1; + #else + res.gl_minor_version = 0; + #endif + #endif + } + res.html5_canvas_selector = _sapp_def(res.html5_canvas_selector, "#canvas"); + res.clipboard_size = _sapp_def(res.clipboard_size, 8192); + res.max_dropped_files = _sapp_def(res.max_dropped_files, 1); + res.max_dropped_file_path_length = _sapp_def(res.max_dropped_file_path_length, 2048); + res.window_title = _sapp_def(res.window_title, "sokol"); + return res; +} + +_SOKOL_PRIVATE void _sapp_init_state(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->width >= 0); + SOKOL_ASSERT(desc->height >= 0); + SOKOL_ASSERT(desc->sample_count >= 0); + SOKOL_ASSERT(desc->swap_interval >= 0); + SOKOL_ASSERT(desc->clipboard_size >= 0); + SOKOL_ASSERT(desc->max_dropped_files >= 0); + SOKOL_ASSERT(desc->max_dropped_file_path_length >= 0); + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); + _sapp.desc = _sapp_desc_defaults(desc); + _sapp.first_frame = true; + // NOTE: _sapp.desc.width/height may be 0! Platform backends need to deal with this + _sapp.window_width = _sapp.desc.width; + _sapp.window_height = _sapp.desc.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp.sample_count = _sapp.desc.sample_count; + _sapp.swap_interval = _sapp.desc.swap_interval; + _sapp_strcpy(_sapp.desc.html5_canvas_selector, _sapp.html5_canvas_selector, sizeof(_sapp.html5_canvas_selector)); + _sapp.desc.html5_canvas_selector = _sapp.html5_canvas_selector; + _sapp.html5_ask_leave_site = _sapp.desc.html5_ask_leave_site; + _sapp.clipboard.enabled = _sapp.desc.enable_clipboard; + if (_sapp.clipboard.enabled) { + _sapp.clipboard.buf_size = _sapp.desc.clipboard_size; + _sapp.clipboard.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.clipboard.buf_size); + } + _sapp.drop.enabled = _sapp.desc.enable_dragndrop; + if (_sapp.drop.enabled) { + _sapp.drop.max_files = _sapp.desc.max_dropped_files; + _sapp.drop.max_path_length = _sapp.desc.max_dropped_file_path_length; + _sapp.drop.buf_size = _sapp.drop.max_files * _sapp.drop.max_path_length; + _sapp.drop.buffer = (char*) _sapp_malloc_clear((size_t)_sapp.drop.buf_size); + } + _sapp_strcpy(_sapp.desc.window_title, _sapp.window_title, sizeof(_sapp.window_title)); + _sapp.desc.window_title = _sapp.window_title; + _sapp.dpi_scale = 1.0f; + _sapp.fullscreen = _sapp.desc.fullscreen; + _sapp.mouse.shown = true; + _sapp_timing_init(&_sapp.timing); +} + +_SOKOL_PRIVATE void _sapp_discard_state(void) { + if (_sapp.clipboard.enabled) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + _sapp_free((void*)_sapp.clipboard.buffer); + } + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + _sapp_free((void*)_sapp.drop.buffer); + } + if (_sapp.default_icon_pixels) { + _sapp_free((void*)_sapp.default_icon_pixels); + } + _SAPP_CLEAR_ARC_STRUCT(_sapp_t, _sapp); +} + +_SOKOL_PRIVATE void _sapp_init_event(sapp_event_type type) { + _sapp_clear(&_sapp.event, sizeof(_sapp.event)); + _sapp.event.type = type; + _sapp.event.frame_count = _sapp.frame_count; + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + _sapp.event.window_width = _sapp.window_width; + _sapp.event.window_height = _sapp.window_height; + _sapp.event.framebuffer_width = _sapp.framebuffer_width; + _sapp.event.framebuffer_height = _sapp.framebuffer_height; + _sapp.event.mouse_x = _sapp.mouse.x; + _sapp.event.mouse_y = _sapp.mouse.y; + _sapp.event.mouse_dx = _sapp.mouse.dx; + _sapp.event.mouse_dy = _sapp.mouse.dy; +} + +_SOKOL_PRIVATE bool _sapp_events_enabled(void) { + /* only send events when an event callback is set, and the init function was called */ + return (_sapp.desc.event_cb || _sapp.desc.event_userdata_cb) && _sapp.init_called; +} + +_SOKOL_PRIVATE sapp_keycode _sapp_translate_key(int scan_code) { + if ((scan_code >= 0) && (scan_code < SAPP_MAX_KEYCODES)) { + return _sapp.keycodes[scan_code]; + } + else { + return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_clear_drop_buffer(void) { + if (_sapp.drop.enabled) { + SOKOL_ASSERT(_sapp.drop.buffer); + _sapp_clear(_sapp.drop.buffer, (size_t)_sapp.drop.buf_size); + } +} + +_SOKOL_PRIVATE void _sapp_frame(void) { + if (_sapp.first_frame) { + _sapp.first_frame = false; + _sapp_call_init(); + } + _sapp_call_frame(); + _sapp.frame_count++; +} + +_SOKOL_PRIVATE bool _sapp_image_validate(const sapp_image_desc* desc) { + SOKOL_ASSERT(desc->width > 0); + SOKOL_ASSERT(desc->height > 0); + SOKOL_ASSERT(desc->pixels.ptr != 0); + SOKOL_ASSERT(desc->pixels.size > 0); + const size_t wh_size = (size_t)(desc->width * desc->height) * sizeof(uint32_t); + if (wh_size != desc->pixels.size) { + _SAPP_ERROR(IMAGE_DATA_SIZE_MISMATCH); + return false; + } + return true; +} + +_SOKOL_PRIVATE int _sapp_image_bestmatch(const sapp_image_desc image_descs[], int num_images, int width, int height) { + int least_diff = 0x7FFFFFFF; + int least_index = 0; + for (int i = 0; i < num_images; i++) { + int diff = (image_descs[i].width * image_descs[i].height) - (width * height); + if (diff < 0) { + diff = -diff; + } + if (diff < least_diff) { + least_diff = diff; + least_index = i; + } + } + return least_index; +} + +_SOKOL_PRIVATE int _sapp_icon_num_images(const sapp_icon_desc* desc) { + int index = 0; + for (; index < SAPP_MAX_ICONIMAGES; index++) { + if (0 == desc->images[index].pixels.ptr) { + break; + } + } + return index; +} + +_SOKOL_PRIVATE bool _sapp_validate_icon_desc(const sapp_icon_desc* desc, int num_images) { + SOKOL_ASSERT(num_images <= SAPP_MAX_ICONIMAGES); + for (int i = 0; i < num_images; i++) { + const sapp_image_desc* img_desc = &desc->images[i]; + if (!_sapp_image_validate(img_desc)) { + return false; + } + } + return true; +} + +_SOKOL_PRIVATE void _sapp_setup_default_icon(void) { + SOKOL_ASSERT(0 == _sapp.default_icon_pixels); + + const int num_icons = 3; + const int icon_sizes[3] = { 16, 32, 64 }; // must be multiple of 8! + + // allocate a pixel buffer for all icon pixels + int all_num_pixels = 0; + for (int i = 0; i < num_icons; i++) { + all_num_pixels += icon_sizes[i] * icon_sizes[i]; + } + _sapp.default_icon_pixels = (uint32_t*) _sapp_malloc_clear((size_t)all_num_pixels * sizeof(uint32_t)); + + // initialize default_icon_desc struct + uint32_t* dst = _sapp.default_icon_pixels; + const uint32_t* dst_end = dst + all_num_pixels; + (void)dst_end; // silence unused warning in release mode + for (int i = 0; i < num_icons; i++) { + const int dim = (int) icon_sizes[i]; + const int num_pixels = dim * dim; + sapp_image_desc* img_desc = &_sapp.default_icon_desc.images[i]; + img_desc->width = dim; + img_desc->height = dim; + img_desc->pixels.ptr = dst; + img_desc->pixels.size = (size_t)num_pixels * sizeof(uint32_t); + dst += num_pixels; + } + SOKOL_ASSERT(dst == dst_end); + + // Amstrad CPC font 'S' + const uint8_t tile[8] = { + 0x3C, + 0x66, + 0x60, + 0x3C, + 0x06, + 0x66, + 0x3C, + 0x00, + }; + // rainbow colors + const uint32_t colors[8] = { + 0xFF4370FF, + 0xFF26A7FF, + 0xFF58EEFF, + 0xFF57E1D4, + 0xFF65CC9C, + 0xFF6ABB66, + 0xFFF5A542, + 0xFFC2577E, + }; + dst = _sapp.default_icon_pixels; + const uint32_t blank = 0x00FFFFFF; + const uint32_t shadow = 0xFF000000; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + SOKOL_ASSERT((dim % 8) == 0); + const int scale = dim / 8; + for (int ty = 0, y = 0; ty < 8; ty++) { + const uint32_t color = colors[ty]; + for (int sy = 0; sy < scale; sy++, y++) { + uint8_t bits = tile[ty]; + for (int tx = 0, x = 0; tx < 8; tx++, bits<<=1) { + uint32_t pixel = (0 == (bits & 0x80)) ? blank : color; + for (int sx = 0; sx < scale; sx++, x++) { + SOKOL_ASSERT(dst < dst_end); + *dst++ = pixel; + } + } + } + } + } + SOKOL_ASSERT(dst == dst_end); + + // right shadow + dst = _sapp.default_icon_pixels; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + for (int y = 0; y < dim; y++) { + uint32_t prev_color = blank; + for (int x = 0; x < dim; x++) { + const int dst_index = y * dim + x; + const uint32_t cur_color = dst[dst_index]; + if ((cur_color == blank) && (prev_color != blank)) { + dst[dst_index] = shadow; + } + prev_color = cur_color; + } + } + dst += dim * dim; + } + SOKOL_ASSERT(dst == dst_end); + + // bottom shadow + dst = _sapp.default_icon_pixels; + for (int i = 0; i < num_icons; i++) { + const int dim = icon_sizes[i]; + for (int x = 0; x < dim; x++) { + uint32_t prev_color = blank; + for (int y = 0; y < dim; y++) { + const int dst_index = y * dim + x; + const uint32_t cur_color = dst[dst_index]; + if ((cur_color == blank) && (prev_color != blank)) { + dst[dst_index] = shadow; + } + prev_color = cur_color; + } + } + dst += dim * dim; + } + SOKOL_ASSERT(dst == dst_end); +} + +// █████ ██████ ██████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██ █████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ +// +// >>apple +#if defined(_SAPP_APPLE) + +#if __has_feature(objc_arc) +#define _SAPP_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SAPP_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +// ███ ███ █████ ██████ ██████ ███████ +// ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ ███████ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██████ ██████ ███████ +// +// >>macos +#if defined(_SAPP_MACOS) + +_SOKOL_PRIVATE void _sapp_macos_init_keytable(void) { + _sapp.keycodes[0x1D] = SAPP_KEYCODE_0; + _sapp.keycodes[0x12] = SAPP_KEYCODE_1; + _sapp.keycodes[0x13] = SAPP_KEYCODE_2; + _sapp.keycodes[0x14] = SAPP_KEYCODE_3; + _sapp.keycodes[0x15] = SAPP_KEYCODE_4; + _sapp.keycodes[0x17] = SAPP_KEYCODE_5; + _sapp.keycodes[0x16] = SAPP_KEYCODE_6; + _sapp.keycodes[0x1A] = SAPP_KEYCODE_7; + _sapp.keycodes[0x1C] = SAPP_KEYCODE_8; + _sapp.keycodes[0x19] = SAPP_KEYCODE_9; + _sapp.keycodes[0x00] = SAPP_KEYCODE_A; + _sapp.keycodes[0x0B] = SAPP_KEYCODE_B; + _sapp.keycodes[0x08] = SAPP_KEYCODE_C; + _sapp.keycodes[0x02] = SAPP_KEYCODE_D; + _sapp.keycodes[0x0E] = SAPP_KEYCODE_E; + _sapp.keycodes[0x03] = SAPP_KEYCODE_F; + _sapp.keycodes[0x05] = SAPP_KEYCODE_G; + _sapp.keycodes[0x04] = SAPP_KEYCODE_H; + _sapp.keycodes[0x22] = SAPP_KEYCODE_I; + _sapp.keycodes[0x26] = SAPP_KEYCODE_J; + _sapp.keycodes[0x28] = SAPP_KEYCODE_K; + _sapp.keycodes[0x25] = SAPP_KEYCODE_L; + _sapp.keycodes[0x2E] = SAPP_KEYCODE_M; + _sapp.keycodes[0x2D] = SAPP_KEYCODE_N; + _sapp.keycodes[0x1F] = SAPP_KEYCODE_O; + _sapp.keycodes[0x23] = SAPP_KEYCODE_P; + _sapp.keycodes[0x0C] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x0F] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01] = SAPP_KEYCODE_S; + _sapp.keycodes[0x11] = SAPP_KEYCODE_T; + _sapp.keycodes[0x20] = SAPP_KEYCODE_U; + _sapp.keycodes[0x09] = SAPP_KEYCODE_V; + _sapp.keycodes[0x0D] = SAPP_KEYCODE_W; + _sapp.keycodes[0x07] = SAPP_KEYCODE_X; + _sapp.keycodes[0x10] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x06] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x27] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x2A] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x2B] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x18] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x32] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x21] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x1B] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x2F] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x1E] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x29] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x2C] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x0A] = SAPP_KEYCODE_WORLD_1; + _sapp.keycodes[0x33] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x39] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x75] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x7D] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x77] = SAPP_KEYCODE_END; + _sapp.keycodes[0x24] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x35] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x7A] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x78] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x63] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x76] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x60] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x61] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x62] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x64] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x65] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x6D] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x67] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x6F] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x69] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x6B] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x71] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x6A] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x40] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x4F] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x50] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x5A] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x73] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x72] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x7B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x3A] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x3B] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x38] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x37] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x6E] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x47] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x79] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x74] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x7C] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x3D] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x3E] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x3C] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x36] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x31] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x30] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x7E] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x52] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x53] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x54] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x55] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x56] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x57] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x58] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x59] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x5B] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x5C] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x45] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x41] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x4B] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x4C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x51] = SAPP_KEYCODE_KP_EQUAL; + _sapp.keycodes[0x43] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x4E] = SAPP_KEYCODE_KP_SUBTRACT; +} + +_SOKOL_PRIVATE void _sapp_macos_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + if (_sapp.macos.keyup_monitor != nil) { + [NSEvent removeMonitor:_sapp.macos.keyup_monitor]; + // NOTE: removeMonitor also releases the object + _sapp.macos.keyup_monitor = nil; + } + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + _SAPP_OBJC_RELEASE(_sapp.macos.app_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.win_dlg); + _SAPP_OBJC_RELEASE(_sapp.macos.view); + #if defined(SOKOL_METAL) + _SAPP_OBJC_RELEASE(_sapp.macos.mtl_device); + #endif + _SAPP_OBJC_RELEASE(_sapp.macos.window); +} + +// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end + +_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { + _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug + _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; +} + +_SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_macos_init_keytable(); + [NSApplication sharedApplication]; + + // set the application dock icon as early as possible, otherwise + // the dummy icon will be visible for a short time + sapp_set_icon(&_sapp.desc.icon); + _sapp.macos.app_dlg = [[_sapp_macos_app_delegate alloc] init]; + NSApp.delegate = _sapp.macos.app_dlg; + + // workaround for "no key-up sent while Cmd is pressed" taken from GLFW: + NSEvent* (^keyup_monitor)(NSEvent*) = ^NSEvent* (NSEvent* event) { + if ([event modifierFlags] & NSEventModifierFlagCommand) { + [[NSApp keyWindow] sendEvent:event]; + } + return event; + }; + _sapp.macos.keyup_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_monitor]; + + [NSApp run]; + // NOTE: [NSApp run] never returns, instead cleanup code + // must be put into applicationWillTerminate +} + +/* MacOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_macos_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE uint32_t _sapp_macos_mods(NSEvent* ev) { + const NSEventModifierFlags f = (ev == nil) ? NSEvent.modifierFlags : ev.modifierFlags; + const NSUInteger b = NSEvent.pressedMouseButtons; + uint32_t m = 0; + if (f & NSEventModifierFlagShift) { + m |= SAPP_MODIFIER_SHIFT; + } + if (f & NSEventModifierFlagControl) { + m |= SAPP_MODIFIER_CTRL; + } + if (f & NSEventModifierFlagOption) { + m |= SAPP_MODIFIER_ALT; + } + if (f & NSEventModifierFlagCommand) { + m |= SAPP_MODIFIER_SUPER; + } + if (0 != (b & (1<<0))) { + m |= SAPP_MODIFIER_LMB; + } + if (0 != (b & (1<<1))) { + m |= SAPP_MODIFIER_RMB; + } + if (0 != (b & (1<<2))) { + m |= SAPP_MODIFIER_MMB; + } + return m; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mod) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mod; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_macos_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +/* NOTE: unlike the iOS version of this function, the macOS version + can dynamically update the DPI scaling factor when a window is moved + between HighDPI / LowDPI screens. +*/ +_SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) { + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = [_sapp.macos.window screen].backingScaleFactor; + } + else { + _sapp.dpi_scale = 1.0f; + } + _sapp.macos.view.layer.contentsScale = _sapp.dpi_scale; // NOTE: needed because we set layerContentsPlacement to a non-scaling value in windowWillStartLiveResize. + const NSRect bounds = [_sapp.macos.view bounds]; + _sapp.window_width = (int)roundf(bounds.size.width); + _sapp.window_height = (int)roundf(bounds.size.height); + #if defined(SOKOL_METAL) + _sapp.framebuffer_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const CGSize fb_size = _sapp.macos.view.drawableSize; + const int cur_fb_width = (int)roundf(fb_size.width); + const int cur_fb_height = (int)roundf(fb_size.height); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + #elif defined(SOKOL_GLCORE) + const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); + const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + _sapp.framebuffer_width = cur_fb_width; + _sapp.framebuffer_height = cur_fb_height; + #endif + if (_sapp.framebuffer_width == 0) { + _sapp.framebuffer_width = 1; + } + if (_sapp.framebuffer_height == 0) { + _sapp.framebuffer_height = 1; + } + if (_sapp.window_width == 0) { + _sapp.window_width = 1; + } + if (_sapp.window_height == 0) { + _sapp.window_height = 1; + } + if (dim_changed) { + #if defined(SOKOL_METAL) + CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.macos.view.drawableSize = drawable_size; + #else + // nothing to do for GL? + #endif + if (!_sapp.first_frame) { + _sapp_macos_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_macos_toggle_fullscreen(void) { + /* NOTE: the _sapp.fullscreen flag is also notified by the + windowDidEnterFullscreen / windowDidExitFullscreen + event handlers + */ + _sapp.fullscreen = !_sapp.fullscreen; + [_sapp.macos.window toggleFullScreen:nil]; +} + +_SOKOL_PRIVATE void _sapp_macos_set_clipboard_string(const char* str) { + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(str) forType:NSPasteboardTypeString]; + } +} + +_SOKOL_PRIVATE const char* _sapp_macos_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.buffer); + @autoreleasepool { + _sapp.clipboard.buffer[0] = 0; + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) { + return _sapp.clipboard.buffer; + } + NSString* str = [pasteboard stringForType:NSPasteboardTypeString]; + if (!str) { + return _sapp.clipboard.buffer; + } + _sapp_strcpy([str UTF8String], _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + } + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_macos_update_window_title(void) { + [_sapp.macos.window setTitle: [NSString stringWithUTF8String:_sapp.window_title]]; +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_update_from_nspoint(NSPoint mouse_pos, bool clear_dxdy) { + if (!_sapp.mouse.locked) { + float new_x = mouse_pos.x * _sapp.dpi_scale; + float new_y = _sapp.framebuffer_height - (mouse_pos.y * _sapp.dpi_scale) - 1; + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } + else if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first update + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_macos_mouse_update_from_nsevent(NSEvent* event, bool clear_dxdy) { + _sapp_macos_mouse_update_from_nspoint(event.locationInWindow, clear_dxdy); +} + +_SOKOL_PRIVATE void _sapp_macos_show_mouse(bool visible) { + /* NOTE: this function is only called when the mouse visibility actually changes */ + if (visible) { + CGDisplayShowCursor(kCGDirectMainDisplay); + } + else { + CGDisplayHideCursor(kCGDirectMainDisplay); + } +} + +_SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + /* + NOTE that this code doesn't warp the mouse cursor to the window + center as everybody else does it. This lead to a spike in the + *second* mouse-moved event after the warp happened. The + mouse centering doesn't seem to be required (mouse-moved events + are reported correctly even when the cursor is at an edge of the screen). + + NOTE also that the hide/show of the mouse cursor should properly + stack with calls to sapp_show_mouse() + */ + if (_sapp.mouse.locked) { + CGAssociateMouseAndMouseCursorPosition(NO); + [NSCursor hide]; + } + else { + [NSCursor unhide]; + CGAssociateMouseAndMouseCursorPosition(YES); + } +} + +_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { + // show/hide cursor only if visibility status has changed (required because show/hide stacks) + if (shown != _sapp.mouse.shown) { + if (shown) { + [NSCursor unhide]; + } + else { + [NSCursor hide]; + } + } + // update cursor type + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.macos.cursors[cursor]) { + [_sapp.macos.cursors[cursor] set]; + } + else { + [[NSCursor arrowCursor] set]; + } +} + +_SOKOL_PRIVATE void _sapp_macos_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + NSDockTile* dock_tile = NSApp.dockTile; + const int wanted_width = (int) dock_tile.size.width; + const int wanted_height = (int) dock_tile.size.height; + const int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, wanted_width, wanted_height); + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + + CGColorSpaceRef cg_color_space = CGColorSpaceCreateDeviceRGB(); + CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)img_desc->pixels.ptr, (CFIndex)img_desc->pixels.size); + CGDataProviderRef cg_data_provider = CGDataProviderCreateWithCFData(cf_data); + CGImageRef cg_img = CGImageCreate( + (size_t)img_desc->width, // width + (size_t)img_desc->height, // height + 8, // bitsPerComponent + 32, // bitsPerPixel + (size_t)img_desc->width * 4,// bytesPerRow + cg_color_space, // space + kCGImageAlphaLast | kCGImageByteOrderDefault, // bitmapInfo + cg_data_provider, // provider + NULL, // decode + false, // shouldInterpolate + kCGRenderingIntentDefault); + CFRelease(cf_data); + CGDataProviderRelease(cg_data_provider); + CGColorSpaceRelease(cg_color_space); + + NSImage* ns_image = [[NSImage alloc] initWithCGImage:cg_img size:dock_tile.size]; + dock_tile.contentView = [NSImageView imageViewWithImage:ns_image]; + [dock_tile display]; + _SAPP_OBJC_RELEASE(ns_image); + CGImageRelease(cg_img); +} + +_SOKOL_PRIVATE void _sapp_macos_frame(void) { + _sapp_frame(); + if (_sapp.quit_requested || _sapp.quit_ordered) { + [_sapp.macos.window performClose:nil]; + } +} + +@implementation _sapp_macos_app_delegate +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { + _SOKOL_UNUSED(aNotification); + _sapp_macos_init_cursors(); + if ((_sapp.window_width == 0) || (_sapp.window_height == 0)) { + // use 4/5 of screen size as default size + NSRect screen_rect = NSScreen.mainScreen.frame; + if (_sapp.window_width == 0) { + _sapp.window_width = (int)roundf((screen_rect.size.width * 4.0f) / 5.0f); + } + if (_sapp.window_height == 0) { + _sapp.window_height = (int)roundf((screen_rect.size.height * 4.0f) / 5.0f); + } + } + const NSUInteger style = + NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskResizable; + NSRect window_rect = NSMakeRect(0, 0, _sapp.window_width, _sapp.window_height); + _sapp.macos.window = [[_sapp_macos_window alloc] + initWithContentRect:window_rect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + _sapp.macos.window.releasedWhenClosed = NO; // this is necessary for proper cleanup in applicationWillTerminate + _sapp.macos.window.title = [NSString stringWithUTF8String:_sapp.window_title]; + _sapp.macos.window.acceptsMouseMovedEvents = YES; + _sapp.macos.window.restorable = YES; + + _sapp.macos.win_dlg = [[_sapp_macos_window_delegate alloc] init]; + _sapp.macos.window.delegate = _sapp.macos.win_dlg; + #if defined(SOKOL_METAL) + NSInteger max_fps = 60; + #if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) + if (@available(macOS 12.0, *)) { + max_fps = [NSScreen.mainScreen maximumFramesPerSecond]; + } + #endif + _sapp.macos.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.macos.view = [[_sapp_macos_view alloc] init]; + [_sapp.macos.view updateTrackingAreas]; + _sapp.macos.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.macos.view.device = _sapp.macos.mtl_device; + _sapp.macos.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.macos.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.macos.view.sampleCount = (NSUInteger) _sapp.sample_count; + _sapp.macos.view.autoResizeDrawable = false; + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; + #elif defined(SOKOL_GLCORE) + NSOpenGLPixelFormatAttribute attrs[32]; + int i = 0; + attrs[i++] = NSOpenGLPFAAccelerated; + attrs[i++] = NSOpenGLPFADoubleBuffer; + attrs[i++] = NSOpenGLPFAOpenGLProfile; + const int glVersion = _sapp.desc.gl_major_version * 10 + _sapp.desc.gl_minor_version; + switch(glVersion) { + case 10: attrs[i++] = NSOpenGLProfileVersionLegacy; break; + case 32: attrs[i++] = NSOpenGLProfileVersion3_2Core; break; + case 41: attrs[i++] = NSOpenGLProfileVersion4_1Core; break; + default: + _SAPP_PANIC(MACOS_INVALID_NSOPENGL_PROFILE); + } + attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; + attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAStencilSize; attrs[i++] = 8; + if (_sapp.sample_count > 1) { + attrs[i++] = NSOpenGLPFAMultisample; + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; + attrs[i++] = NSOpenGLPFASamples; attrs[i++] = (NSOpenGLPixelFormatAttribute)_sapp.sample_count; + } + else { + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 0; + } + attrs[i++] = 0; + NSOpenGLPixelFormat* glpixelformat_obj = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + SOKOL_ASSERT(glpixelformat_obj != nil); + + _sapp.macos.view = [[_sapp_macos_view alloc] + initWithFrame:window_rect + pixelFormat:glpixelformat_obj]; + _SAPP_OBJC_RELEASE(glpixelformat_obj); + [_sapp.macos.view updateTrackingAreas]; + if (_sapp.desc.high_dpi) { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:YES]; + } + else { + [_sapp.macos.view setWantsBestResolutionOpenGLSurface:NO]; + } + + _sapp.macos.window.contentView = _sapp.macos.view; + [_sapp.macos.window makeFirstResponder:_sapp.macos.view]; + + NSTimer* timer_obj = [NSTimer timerWithTimeInterval:0.001 + target:_sapp.macos.view + selector:@selector(timerFired:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer_obj forMode:NSDefaultRunLoopMode]; + timer_obj = nil; + #endif + [_sapp.macos.window center]; + _sapp.valid = true; + if (_sapp.fullscreen) { + /* ^^^ on GL, this already toggles a rendered frame, so set the valid flag before */ + [_sapp.macos.window toggleFullScreen:self]; + } + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + [NSApp activateIgnoringOtherApps:YES]; + [_sapp.macos.window makeKeyAndOrderFront:nil]; + _sapp_macos_update_dimensions(); + [NSEvent setMouseCoalescingEnabled:NO]; + + // workaround for window not being focused during a long init callback + // for details see: https://github.com/floooh/sokol/pull/982 + // also see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2342 + NSEvent *focusevent = [NSEvent otherEventWithType:NSEventTypeAppKitDefined + location:NSZeroPoint + modifierFlags:0x40 + timestamp:0 + windowNumber:0 + context:nil + subtype:NSEventSubtypeApplicationActivated + data1:0 + data2:0]; + [NSApp postEvent:focusevent atStart:YES]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { + _SOKOL_UNUSED(sender); + return YES; +} + +- (void)applicationWillTerminate:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_call_cleanup(); + _sapp_macos_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_macos_window_delegate +- (BOOL)windowShouldClose:(id)sender { + _SOKOL_UNUSED(sender); + /* only give user-code a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a chance to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_macos_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + return YES; + } + else { + return NO; + } +} + +#if defined(SOKOL_METAL) +- (void)windowWillStartLiveResize:(NSNotification *)notification { + // Work around the MTKView resizing glitch by "anchoring" the layer to the window corner opposite + // to the currently manipulated corner (or edge). This prevents the content stretching back and + // forth during resizing. This is a workaround for this issue: https://github.com/floooh/sokol/issues/700 + // Can be removed if/when migrating to CAMetalLayer: https://github.com/floooh/sokol/issues/727 + bool resizing_from_left = _sapp.mouse.x < _sapp.window_width/2; + bool resizing_from_top = _sapp.mouse.y < _sapp.window_height/2; + NSViewLayerContentsPlacement placement; + if (resizing_from_left) { + placement = resizing_from_top ? NSViewLayerContentsPlacementBottomRight : NSViewLayerContentsPlacementTopRight; + } else { + placement = resizing_from_top ? NSViewLayerContentsPlacementBottomLeft : NSViewLayerContentsPlacementTopLeft; + } + _sapp.macos.view.layerContentsPlacement = placement; +} +#endif + +- (void)windowDidResize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_update_dimensions(); +} + +- (void)windowDidChangeScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_timing_reset(&_sapp.timing); + _sapp_macos_update_dimensions(); +} + +- (void)windowDidMiniaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_ICONIFIED); +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_RESTORED); +} + +- (void)windowDidBecomeKey:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_FOCUSED); +} + +- (void)windowDidResignKey:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp_macos_app_event(SAPP_EVENTTYPE_UNFOCUSED); +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = true; +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification { + _SOKOL_UNUSED(notification); + _sapp.fullscreen = false; +} +@end + +@implementation _sapp_macos_window +- (instancetype)initWithContentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType + defer:(BOOL)flag { + if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; + #endif + } + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender { + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + NSPasteboard *pboard = [sender draggingPasteboard]; + if ([pboard.types containsObject:NSPasteboardTypeFileURL]) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = ((int)pboard.pasteboardItems.count > _sapp.drop.max_files) ? _sapp.drop.max_files : (int)pboard.pasteboardItems.count; + bool drop_failed = false; + for (int i = 0; i < _sapp.drop.num_files; i++) { + NSURL *fileUrl = [NSURL fileURLWithPath:[pboard.pasteboardItems[(NSUInteger)i] stringForType:NSPasteboardTypeFileURL]]; + if (!_sapp_strcpy(fileUrl.standardizedURL.path.UTF8String, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + drop_failed = true; + break; + } + } + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_macos_mouse_update_from_nspoint(sender.draggingLocation, true); + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp.event.modifiers = _sapp_macos_mods(nil); + _sapp_call_event(&_sapp.event); + } + } + else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } + return YES; + } + #endif + return NO; +} +@end + +@implementation _sapp_macos_view +#if defined(SOKOL_GLCORE) +- (void)timerFired:(id)sender { + _SOKOL_UNUSED(sender); + [self setNeedsDisplay:YES]; +} +- (void)prepareOpenGL { + [super prepareOpenGL]; + GLint swapInt = 1; + NSOpenGLContext* ctx = [_sapp.macos.view openGLContext]; + [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval]; + [ctx makeCurrentContext]; +} +#endif + +_SOKOL_PRIVATE void _sapp_macos_poll_input_events(void) { + /* + + NOTE: late event polling temporarily out-commented to check if this + causes infrequent and almost impossible to reproduce problems with the + window close events, see: + https://github.com/floooh/sokol/pull/483#issuecomment-805148815 + + + const NSEventMask mask = NSEventMaskLeftMouseDown | + NSEventMaskLeftMouseUp| + NSEventMaskRightMouseDown | + NSEventMaskRightMouseUp | + NSEventMaskMouseMoved | + NSEventMaskLeftMouseDragged | + NSEventMaskRightMouseDragged | + NSEventMaskMouseEntered | + NSEventMaskMouseExited | + NSEventMaskKeyDown | + NSEventMaskKeyUp | + NSEventMaskCursorUpdate | + NSEventMaskScrollWheel | + NSEventMaskTabletPoint | + NSEventMaskTabletProximity | + NSEventMaskOtherMouseDown | + NSEventMaskOtherMouseUp | + NSEventMaskOtherMouseDragged | + NSEventMaskPressure | + NSEventMaskDirectTouch; + @autoreleasepool { + for (;;) { + // NOTE: using NSDefaultRunLoopMode here causes stuttering in the GL backend, + // see: https://github.com/floooh/sokol/issues/486 + NSEvent* event = [NSApp nextEventMatchingMask:mask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES]; + if (event == nil) { + break; + } + [NSApp sendEvent:event]; + } + } + */ +} + +- (void)drawRect:(NSRect)rect { + _SOKOL_UNUSED(rect); + #if defined(_SAPP_ANY_GL) + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + #endif + _sapp_timing_measure(&_sapp.timing); + /* Catch any last-moment input events */ + _sapp_macos_poll_input_events(); + @autoreleasepool { + _sapp_macos_frame(); + } + #if defined(_SAPP_ANY_GL) + [[_sapp.macos.view openGLContext] flushBuffer]; + #endif +} + +- (BOOL)isOpaque { + return YES; +} +- (BOOL)canBecomeKeyView { + return YES; +} +- (BOOL)acceptsFirstResponder { + return YES; +} +- (void)updateTrackingAreas { + if (_sapp.macos.tracking_area != nil) { + [self removeTrackingArea:_sapp.macos.tracking_area]; + _SAPP_OBJC_RELEASE(_sapp.macos.tracking_area); + } + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + _sapp.macos.tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + [self addTrackingArea:_sapp.macos.tracking_area]; + [super updateTrackingAreas]; +} + +// helper function to make GL context active +static void _sapp_gl_make_current(void) { + #if defined(SOKOL_GLCORE) + [[_sapp.macos.view openGLContext] makeCurrentContext]; + #endif +} + +- (void)mouseEntered:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, true); + /* don't send mouse enter/leave while dragging (so that it behaves the same as + on Windows while SetCapture is active + */ + if (0 == _sapp.macos.mouse_buttons) { + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); + } +} +- (void)mouseExited:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, true); + if (0 == _sapp.macos.mouse_buttons) { + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_macos_mods(event)); + } +} +- (void)mouseDown:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_mouse_update_from_nsevent(event, false); + _sapp_macos_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT, _sapp_macos_mods(event)); + _sapp.macos.mouse_buttons |= (1< 0.0f) || (_sapp_absf(dy) > 0.0f)) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_macos_mods(event); + _sapp.event.scroll_x = dx; + _sapp.event.scroll_y = dy; + _sapp_call_event(&_sapp.event); + } + } +} +- (void)keyDown:(NSEvent*)event { + if (_sapp_events_enabled()) { + _sapp_gl_make_current(); + const uint32_t mods = _sapp_macos_mods(event); + const sapp_keycode key_code = _sapp_translate_key(event.keyCode); + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_DOWN, key_code, event.isARepeat, mods); + const NSString* chars = event.characters; + const NSUInteger len = chars.length; + if (len > 0) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = mods; + for (NSUInteger i = 0; i < len; i++) { + const unichar codepoint = [chars characterAtIndex:i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + _sapp.event.char_code = codepoint; + _sapp.event.key_repeat = event.isARepeat; + _sapp_call_event(&_sapp.event); + } + } + /* if this is a Cmd+V (paste), also send a CLIPBOARD_PASTE event */ + if (_sapp.clipboard.enabled && (mods == SAPP_MODIFIER_SUPER) && (key_code == SAPP_KEYCODE_V)) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +- (BOOL)performKeyEquivalent:(NSEvent*)event { + // fixes Ctrl-Tab keydown not triggering a keyDown event + // + // NOTE: it seems that Ctrl-F1 cannot be intercepted the same way, but since + // this enabled critical accessibility features that's probably a good thing. + switch (_sapp_translate_key(event.keyCode)) { + case SAPP_KEYCODE_TAB: + [_sapp.macos.view keyDown:event]; + return YES; + default: + return NO; + } +} + +- (void)keyUp:(NSEvent*)event { + _sapp_gl_make_current(); + _sapp_macos_key_event(SAPP_EVENTTYPE_KEY_UP, + _sapp_translate_key(event.keyCode), + event.isARepeat, + _sapp_macos_mods(event)); +} +- (void)flagsChanged:(NSEvent*)event { + const uint32_t old_f = _sapp.macos.flags_changed_store; + const uint32_t new_f = (uint32_t)event.modifierFlags; + _sapp.macos.flags_changed_store = new_f; + sapp_keycode key_code = SAPP_KEYCODE_INVALID; + bool down = false; + if ((new_f ^ old_f) & NSEventModifierFlagShift) { + key_code = SAPP_KEYCODE_LEFT_SHIFT; + down = 0 != (new_f & NSEventModifierFlagShift); + } + if ((new_f ^ old_f) & NSEventModifierFlagControl) { + key_code = SAPP_KEYCODE_LEFT_CONTROL; + down = 0 != (new_f & NSEventModifierFlagControl); + } + if ((new_f ^ old_f) & NSEventModifierFlagOption) { + key_code = SAPP_KEYCODE_LEFT_ALT; + down = 0 != (new_f & NSEventModifierFlagOption); + } + if ((new_f ^ old_f) & NSEventModifierFlagCommand) { + key_code = SAPP_KEYCODE_LEFT_SUPER; + down = 0 != (new_f & NSEventModifierFlagCommand); + } + if (key_code != SAPP_KEYCODE_INVALID) { + _sapp_macos_key_event(down ? SAPP_EVENTTYPE_KEY_DOWN : SAPP_EVENTTYPE_KEY_UP, + key_code, + false, + _sapp_macos_mods(event)); + } +} +@end + +#endif // macOS + +// ██ ██████ ███████ +// ██ ██ ██ ██ +// ██ ██ ██ ███████ +// ██ ██ ██ ██ +// ██ ██████ ███████ +// +// >>ios +#if defined(_SAPP_IOS) + +_SOKOL_PRIVATE void _sapp_ios_discard_state(void) { + // NOTE: it's safe to call [release] on a nil object + _SAPP_OBJC_RELEASE(_sapp.ios.textfield_dlg); + _SAPP_OBJC_RELEASE(_sapp.ios.textfield); + #if defined(SOKOL_METAL) + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.mtl_device); + #else + _SAPP_OBJC_RELEASE(_sapp.ios.view_ctrl); + _SAPP_OBJC_RELEASE(_sapp.ios.eagl_ctx); + #endif + _SAPP_OBJC_RELEASE(_sapp.ios.view); + _SAPP_OBJC_RELEASE(_sapp.ios.window); +} + +_SOKOL_PRIVATE void _sapp_ios_run(const sapp_desc* desc) { + _sapp_init_state(desc); + static int argc = 1; + static char* argv[] = { (char*)"sokol_app" }; + UIApplicationMain(argc, argv, nil, NSStringFromClass([_sapp_app_delegate class])); +} + +/* iOS entry function */ +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_ios_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ + +_SOKOL_PRIVATE void _sapp_ios_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_ios_touch_event(sapp_event_type type, NSSet* touches, UIEvent* event) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + NSEnumerator* enumerator = event.allTouches.objectEnumerator; + UITouch* ios_touch; + while ((ios_touch = [enumerator nextObject])) { + if ((_sapp.event.num_touches + 1) < SAPP_MAX_TOUCHPOINTS) { + CGPoint ios_pos = [ios_touch locationInView:_sapp.ios.view]; + sapp_touchpoint* cur_point = &_sapp.event.touches[_sapp.event.num_touches++]; + cur_point->identifier = (uintptr_t) ios_touch; + cur_point->pos_x = ios_pos.x * _sapp.dpi_scale; + cur_point->pos_y = ios_pos.y * _sapp.dpi_scale; + cur_point->changed = [touches containsObject:ios_touch]; + } + } + if (_sapp.event.num_touches > 0) { + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_update_dimensions(void) { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.framebuffer_width = (int)roundf(screen_rect.size.width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(screen_rect.size.height * _sapp.dpi_scale); + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); + int cur_fb_width, cur_fb_height; + #if defined(SOKOL_METAL) + const CGSize fb_size = _sapp.ios.view.drawableSize; + cur_fb_width = (int)roundf(fb_size.width); + cur_fb_height = (int)roundf(fb_size.height); + #else + cur_fb_width = (int)roundf(_sapp.ios.view.drawableWidth); + cur_fb_height = (int)roundf(_sapp.ios.view.drawableHeight); + #endif + const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || + (_sapp.framebuffer_height != cur_fb_height); + if (dim_changed) { + #if defined(SOKOL_METAL) + const CGSize drawable_size = { (CGFloat) _sapp.framebuffer_width, (CGFloat) _sapp.framebuffer_height }; + _sapp.ios.view.drawableSize = drawable_size; + #else + // nothing to do here, GLKView correctly respects the view's contentScaleFactor + #endif + if (!_sapp.first_frame) { + _sapp_ios_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_ios_frame(void) { + _sapp_ios_update_dimensions(); + _sapp_frame(); +} + +_SOKOL_PRIVATE void _sapp_ios_show_keyboard(bool shown) { + /* if not happened yet, create an invisible text field */ + if (nil == _sapp.ios.textfield) { + _sapp.ios.textfield_dlg = [[_sapp_textfield_dlg alloc] init]; + _sapp.ios.textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 50)]; + _sapp.ios.textfield.keyboardType = UIKeyboardTypeDefault; + _sapp.ios.textfield.returnKeyType = UIReturnKeyDefault; + _sapp.ios.textfield.autocapitalizationType = UITextAutocapitalizationTypeNone; + _sapp.ios.textfield.autocorrectionType = UITextAutocorrectionTypeNo; + _sapp.ios.textfield.spellCheckingType = UITextSpellCheckingTypeNo; + _sapp.ios.textfield.hidden = YES; + _sapp.ios.textfield.text = @"x"; + _sapp.ios.textfield.delegate = _sapp.ios.textfield_dlg; + [_sapp.ios.view_ctrl.view addSubview:_sapp.ios.textfield]; + + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWasShown:) + name:UIKeyboardDidShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardWillBeHidden:) + name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:_sapp.ios.textfield_dlg + selector:@selector(keyboardDidChangeFrame:) + name:UIKeyboardDidChangeFrameNotification object:nil]; + } + if (shown) { + /* setting the text field as first responder brings up the onscreen keyboard */ + [_sapp.ios.textfield becomeFirstResponder]; + } + else { + [_sapp.ios.textfield resignFirstResponder]; + } +} + +@implementation _sapp_app_delegate +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + CGRect screen_rect = UIScreen.mainScreen.bounds; + _sapp.ios.window = [[UIWindow alloc] initWithFrame:screen_rect]; + _sapp.window_width = (int)roundf(screen_rect.size.width); + _sapp.window_height = (int)roundf(screen_rect.size.height); + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = (float) UIScreen.mainScreen.nativeScale; + } + else { + _sapp.dpi_scale = 1.0f; + } + _sapp.framebuffer_width = (int)roundf(_sapp.window_width * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(_sapp.window_height * _sapp.dpi_scale); + NSInteger max_fps = UIScreen.mainScreen.maximumFramesPerSecond; + #if defined(SOKOL_METAL) + _sapp.ios.mtl_device = MTLCreateSystemDefaultDevice(); + _sapp.ios.view = [[_sapp_ios_view alloc] init]; + _sapp.ios.view.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.ios.view.device = _sapp.ios.mtl_device; + _sapp.ios.view.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + _sapp.ios.view.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _sapp.ios.view.sampleCount = (NSUInteger)_sapp.sample_count; + /* NOTE: iOS MTKView seems to ignore thew view's contentScaleFactor + and automatically renders at Retina resolution. We'll disable + autoResize and instead do the resizing in _sapp_ios_update_dimensions() + */ + _sapp.ios.view.autoResizeDrawable = false; + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + _sapp.ios.view_ctrl = [[UIViewController alloc] init]; + _sapp.ios.view_ctrl.modalPresentationStyle = UIModalPresentationFullScreen; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; + #else + _sapp.ios.eagl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + _sapp.ios.view = [[_sapp_ios_view alloc] initWithFrame:screen_rect]; + _sapp.ios.view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; + _sapp.ios.view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + _sapp.ios.view.drawableStencilFormat = GLKViewDrawableStencilFormatNone; + GLKViewDrawableMultisample msaa = _sapp.sample_count > 1 ? GLKViewDrawableMultisample4X : GLKViewDrawableMultisampleNone; + _sapp.ios.view.drawableMultisample = msaa; + _sapp.ios.view.context = _sapp.ios.eagl_ctx; + _sapp.ios.view.enableSetNeedsDisplay = NO; + _sapp.ios.view.userInteractionEnabled = YES; + _sapp.ios.view.multipleTouchEnabled = YES; + // on GLKView, contentScaleFactor appears to work just fine! + if (_sapp.desc.high_dpi) { + _sapp.ios.view.contentScaleFactor = _sapp.dpi_scale; + } + else { + _sapp.ios.view.contentScaleFactor = 1.0; + } + _sapp.ios.view_ctrl = [[GLKViewController alloc] init]; + _sapp.ios.view_ctrl.view = _sapp.ios.view; + _sapp.ios.view_ctrl.preferredFramesPerSecond = max_fps / _sapp.swap_interval; + _sapp.ios.window.rootViewController = _sapp.ios.view_ctrl; + #endif + [_sapp.ios.window makeKeyAndVisible]; + + _sapp.valid = true; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + if (!_sapp.ios.suspended) { + _sapp.ios.suspended = true; + _sapp_ios_app_event(SAPP_EVENTTYPE_SUSPENDED); + } +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + if (_sapp.ios.suspended) { + _sapp.ios.suspended = false; + _sapp_ios_app_event(SAPP_EVENTTYPE_RESUMED); + } +} + +/* NOTE: this method will rarely ever be called, iOS application + which are terminated by the user are usually killed via signal 9 + by the operating system. +*/ +- (void)applicationWillTerminate:(UIApplication *)application { + _SOKOL_UNUSED(application); + _sapp_call_cleanup(); + _sapp_ios_discard_state(); + _sapp_discard_state(); +} +@end + +@implementation _sapp_textfield_dlg +- (void)keyboardWasShown:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = true; + /* query the keyboard's size, and modify the content view's size */ + if (_sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +} +- (void)keyboardWillBeHidden:(NSNotification*)notif { + _sapp.onscreen_keyboard_shown = false; + if (_sapp.desc.ios_keyboard_resizes_canvas) { + _sapp.ios.view.frame = UIScreen.mainScreen.bounds; + } +} +- (void)keyboardDidChangeFrame:(NSNotification*)notif { + /* this is for the case when the screen rotation changes while the keyboard is open */ + if (_sapp.onscreen_keyboard_shown && _sapp.desc.ios_keyboard_resizes_canvas) { + NSDictionary* info = notif.userInfo; + CGFloat kbd_h = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; + CGRect view_frame = UIScreen.mainScreen.bounds; + view_frame.size.height -= kbd_h; + _sapp.ios.view.frame = view_frame; + } +} +- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string { + if (_sapp_events_enabled()) { + const NSUInteger len = string.length; + if (len > 0) { + for (NSUInteger i = 0; i < len; i++) { + unichar c = [string characterAtIndex:i]; + if (c >= 32) { + /* ignore surrogates for now */ + if ((c < 0xD800) || (c > 0xDFFF)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = c; + _sapp_call_event(&_sapp.event); + } + } + if (c <= 32) { + sapp_keycode k = SAPP_KEYCODE_INVALID; + switch (c) { + case 10: k = SAPP_KEYCODE_ENTER; break; + case 32: k = SAPP_KEYCODE_SPACE; break; + default: break; + } + if (k != SAPP_KEYCODE_INVALID) { + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = k; + _sapp_call_event(&_sapp.event); + } + } + } + } + else { + /* this was a backspace */ + _sapp_init_event(SAPP_EVENTTYPE_KEY_DOWN); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + _sapp_init_event(SAPP_EVENTTYPE_KEY_UP); + _sapp.event.key_code = SAPP_KEYCODE_BACKSPACE; + _sapp_call_event(&_sapp.event); + } + } + return NO; +} +@end + +@implementation _sapp_ios_view +- (void)drawRect:(CGRect)rect { + _SOKOL_UNUSED(rect); + #if defined(_SAPP_ANY_GL) + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + #endif + _sapp_timing_measure(&_sapp.timing); + @autoreleasepool { + _sapp_ios_frame(); + } +} +- (BOOL)isOpaque { + return YES; +} +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_BEGAN, touches, event); +} +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_MOVED, touches, event); +} +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_ENDED, touches, event); +} +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { + _sapp_ios_touch_event(SAPP_EVENTTYPE_TOUCHES_CANCELLED, touches, event); +} +@end +#endif /* TARGET_OS_IPHONE */ + +#endif /* _SAPP_APPLE */ + +// ███████ ███ ███ ███████ ██████ ██████ ██ ██████ ████████ ███████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// █████ ██ ████ ██ ███████ ██ ██████ ██ ██████ ██ █████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ███████ ██ ████ +// +// >>emscripten +#if defined(_SAPP_EMSCRIPTEN) + +#if defined(EM_JS_DEPS) +EM_JS_DEPS(sokol_app, "$withStackSave,$stringToUTF8OnStack,$findCanvasEventTarget") +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*_sapp_html5_fetch_callback) (const sapp_html5_fetch_response*); + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_onpaste(const char* str) { + if (_sapp.clipboard.enabled) { + _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +/* https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload */ +EMSCRIPTEN_KEEPALIVE int _sapp_html5_get_ask_leave_site(void) { + return _sapp.html5_ask_leave_site ? 1 : 0; +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_begin_drop(int num) { + if (!_sapp.drop.enabled) { + return; + } + if (num < 0) { + num = 0; + } + if (num > _sapp.drop.max_files) { + num = _sapp.drop.max_files; + } + _sapp.drop.num_files = num; + _sapp_clear_drop_buffer(); +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_drop(int i, const char* name) { + /* NOTE: name is only the filename part, not a path */ + if (!_sapp.drop.enabled) { + return; + } + if (0 == name) { + return; + } + SOKOL_ASSERT(_sapp.drop.num_files <= _sapp.drop.max_files); + if ((i < 0) || (i >= _sapp.drop.num_files)) { + return; + } + if (!_sapp_strcpy(name, _sapp_dropped_file_path_ptr(i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + _sapp.drop.num_files = 0; + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_end_drop(int x, int y, int mods) { + if (!_sapp.drop.enabled) { + return; + } + if (0 == _sapp.drop.num_files) { + /* there was an error copying the filenames */ + _sapp_clear_drop_buffer(); + return; + + } + if (_sapp_events_enabled()) { + _sapp.mouse.x = (float)x * _sapp.dpi_scale; + _sapp.mouse.y = (float)y * _sapp.dpi_scale; + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + // see sapp_js_add_dragndrop_listeners for mods constants + if (mods & 1) { _sapp.event.modifiers |= SAPP_MODIFIER_SHIFT; } + if (mods & 2) { _sapp.event.modifiers |= SAPP_MODIFIER_CTRL; } + if (mods & 4) { _sapp.event.modifiers |= SAPP_MODIFIER_ALT; } + if (mods & 8) { _sapp.event.modifiers |= SAPP_MODIFIER_SUPER; } + _sapp_call_event(&_sapp.event); + } +} + +EMSCRIPTEN_KEEPALIVE void _sapp_emsc_invoke_fetch_cb(int index, int success, int error_code, _sapp_html5_fetch_callback callback, uint32_t fetched_size, void* buf_ptr, uint32_t buf_size, void* user_data) { + sapp_html5_fetch_response response; + _sapp_clear(&response, sizeof(response)); + response.succeeded = (0 != success); + response.error_code = (sapp_html5_fetch_error) error_code; + response.file_index = index; + response.data.ptr = buf_ptr; + response.data.size = fetched_size; + response.buffer.ptr = buf_ptr; + response.buffer.size = buf_size; + response.user_data = user_data; + callback(&response); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +EM_JS(void, sapp_js_add_beforeunload_listener, (void), { + Module.sokol_beforeunload = (event) => { + if (__sapp_html5_get_ask_leave_site() != 0) { + event.preventDefault(); + event.returnValue = ' '; + } + }; + window.addEventListener('beforeunload', Module.sokol_beforeunload); +}) + +EM_JS(void, sapp_js_remove_beforeunload_listener, (void), { + window.removeEventListener('beforeunload', Module.sokol_beforeunload); +}) + +EM_JS(void, sapp_js_add_clipboard_listener, (void), { + Module.sokol_paste = (event) => { + const pasted_str = event.clipboardData.getData('text'); + withStackSave(() => { + const cstr = stringToUTF8OnStack(pasted_str); + __sapp_emsc_onpaste(cstr); + }); + }; + window.addEventListener('paste', Module.sokol_paste); +}) + +EM_JS(void, sapp_js_remove_clipboard_listener, (void), { + window.removeEventListener('paste', Module.sokol_paste); +}) + +EM_JS(void, sapp_js_write_clipboard, (const char* c_str), { + const str = UTF8ToString(c_str); + const ta = document.createElement('textarea'); + ta.setAttribute('autocomplete', 'off'); + ta.setAttribute('autocorrect', 'off'); + ta.setAttribute('autocapitalize', 'off'); + ta.setAttribute('spellcheck', 'false'); + ta.style.left = -100 + 'px'; + ta.style.top = -100 + 'px'; + ta.style.height = 1; + ta.style.width = 1; + ta.value = str; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); +}) + +_SOKOL_PRIVATE void _sapp_emsc_set_clipboard_string(const char* str) { + sapp_js_write_clipboard(str); +} + +EM_JS(void, sapp_js_add_dragndrop_listeners, (void), { + Module.sokol_drop_files = []; + Module.sokol_dragenter = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragleave = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_dragover = (event) => { + event.stopPropagation(); + event.preventDefault(); + }; + Module.sokol_drop = (event) => { + event.stopPropagation(); + event.preventDefault(); + const files = event.dataTransfer.files; + Module.sokol_dropped_files = files; + __sapp_emsc_begin_drop(files.length); + for (let i = 0; i < files.length; i++) { + withStackSave(() => { + const cstr = stringToUTF8OnStack(files[i].name); + __sapp_emsc_drop(i, cstr); + }); + } + let mods = 0; + if (event.shiftKey) { mods |= 1; } + if (event.ctrlKey) { mods |= 2; } + if (event.altKey) { mods |= 4; } + if (event.metaKey) { mods |= 8; } + // FIXME? see computation of targetX/targetY in emscripten via getClientBoundingRect + __sapp_emsc_end_drop(event.clientX, event.clientY, mods); + }; + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const canvas = Module.sapp_emsc_target; + canvas.addEventListener('dragenter', Module.sokol_dragenter, false); + canvas.addEventListener('dragleave', Module.sokol_dragleave, false); + canvas.addEventListener('dragover', Module.sokol_dragover, false); + canvas.addEventListener('drop', Module.sokol_drop, false); +}) + +EM_JS(uint32_t, sapp_js_dropped_file_size, (int index), { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + if ((index < 0) || (index >= files.length)) { + return 0; + } + else { + return files[index].size; + } +}) + +EM_JS(void, sapp_js_fetch_dropped_file, (int index, _sapp_html5_fetch_callback callback, void* buf_ptr, uint32_t buf_size, void* user_data), { + const reader = new FileReader(); + reader.onload = (loadEvent) => { + const content = loadEvent.target.result; + if (content.byteLength > buf_size) { + // SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL + __sapp_emsc_invoke_fetch_cb(index, 0, 1, callback, 0, buf_ptr, buf_size, user_data); + } + else { + HEAPU8.set(new Uint8Array(content), buf_ptr); + __sapp_emsc_invoke_fetch_cb(index, 1, 0, callback, content.byteLength, buf_ptr, buf_size, user_data); + } + }; + reader.onerror = () => { + // SAPP_HTML5_FETCH_ERROR_OTHER + __sapp_emsc_invoke_fetch_cb(index, 0, 2, callback, 0, buf_ptr, buf_size, user_data); + }; + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const files = Module.sokol_dropped_files; + reader.readAsArrayBuffer(files[index]); +}) + +EM_JS(void, sapp_js_remove_dragndrop_listeners, (void), { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const canvas = Module.sapp_emsc_target; + canvas.removeEventListener('dragenter', Module.sokol_dragenter); + canvas.removeEventListener('dragleave', Module.sokol_dragleave); + canvas.removeEventListener('dragover', Module.sokol_dragover); + canvas.removeEventListener('drop', Module.sokol_drop); +}) + +EM_JS(void, sapp_js_init, (const char* c_str_target_selector, const char* c_str_document_title), { + if (c_str_document_title !== 0) { + document.title = UTF8ToString(c_str_document_title); + } + const target_selector_str = UTF8ToString(c_str_target_selector); + if (Module['canvas'] !== undefined) { + if (typeof Module['canvas'] === 'object') { + specialHTMLTargets[target_selector_str] = Module['canvas']; + } else { + console.warn("sokol_app.h: Module['canvas'] is set but is not an object"); + } + } + Module.sapp_emsc_target = findCanvasEventTarget(target_selector_str); + if (!Module.sapp_emsc_target) { + console.warn("sokol_app.h: can't find html5_canvas_selector ", target_selector_str); + } + if (!Module.sapp_emsc_target.requestPointerLock) { + console.warn("sokol_app.h: target doesn't support requestPointerLock: ", target_selector_str); + } +}) + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockchange_cb(int emsc_type, const EmscriptenPointerlockChangeEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = emsc_event->isActive; + return EM_TRUE; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_pointerlockerror_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + _sapp.mouse.locked = false; + _sapp.emsc.mouse_lock_requested = false; + return true; +} + +EM_JS(void, sapp_js_request_pointerlock, (void), { + if (Module.sapp_emsc_target) { + if (Module.sapp_emsc_target.requestPointerLock) { + Module.sapp_emsc_target.requestPointerLock(); + } + } +}) + +EM_JS(void, sapp_js_exit_pointerlock, (void), { + if (document.exitPointerLock) { + document.exitPointerLock(); + } +}) + +_SOKOL_PRIVATE void _sapp_emsc_lock_mouse(bool lock) { + if (lock) { + /* request mouse-lock during event handler invocation (see _sapp_emsc_update_mouse_lock_state) */ + _sapp.emsc.mouse_lock_requested = true; + } + else { + /* NOTE: the _sapp.mouse_locked state will be set in the pointerlockchange callback */ + _sapp.emsc.mouse_lock_requested = false; + sapp_js_exit_pointerlock(); + } +} + +/* called from inside event handlers to check if mouse lock had been requested, + and if yes, actually enter mouse lock. +*/ +_SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { + if (_sapp.emsc.mouse_lock_requested) { + _sapp.emsc.mouse_lock_requested = false; + sapp_js_request_pointerlock(); + } +} + +// set mouse cursor type +EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), { + if (Module.sapp_emsc_target) { + let cursor; + if (shown === 0) { + cursor = "none"; + } + else switch (cursor_type) { + case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT + case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW + case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM + case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR + case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND + case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW + case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS + case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE + case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW + case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL + case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED + default: cursor = "auto"; break; + } + Module.sapp_emsc_target.style.cursor = cursor; + } +}) + +_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + sapp_js_set_cursor((int)cursor, shown ? 1 : 0); +} + +/* JS helper functions to update browser tab favicon */ +EM_JS(void, sapp_js_clear_favicon, (void), { + const link = document.getElementById('sokol-app-favicon'); + if (link) { + document.head.removeChild(link); + } +}) + +EM_JS(void, sapp_js_set_favicon, (int w, int h, const uint8_t* pixels), { + const canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + const img_data = ctx.createImageData(w, h); + img_data.data.set(HEAPU8.subarray(pixels, pixels + w*h*4)); + ctx.putImageData(img_data, 0, 0); + const new_link = document.createElement('link'); + new_link.id = 'sokol-app-favicon'; + new_link.rel = 'shortcut icon'; + new_link.href = canvas.toDataURL(); + document.head.appendChild(new_link); +}) + +_SOKOL_PRIVATE void _sapp_emsc_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + sapp_js_clear_favicon(); + // find the best matching image candidate for 16x16 pixels + int img_index = _sapp_image_bestmatch(icon_desc->images, num_images, 16, 16); + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + sapp_js_set_favicon(img_desc->width, img_desc->height, (const uint8_t*) img_desc->pixels.ptr); +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_button_mods(uint16_t buttons) { + uint32_t m = 0; + if (0 != (buttons & (1<<0))) { m |= SAPP_MODIFIER_LMB; } + if (0 != (buttons & (1<<1))) { m |= SAPP_MODIFIER_RMB; } // not a bug + if (0 != (buttons & (1<<2))) { m |= SAPP_MODIFIER_MMB; } // not a bug + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_mouse_event_mods(const EmscriptenMouseEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_key_event_mods(const EmscriptenKeyboardEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +_SOKOL_PRIVATE uint32_t _sapp_emsc_touch_event_mods(const EmscriptenTouchEvent* ev) { + uint32_t m = 0; + if (ev->ctrlKey) { m |= SAPP_MODIFIER_CTRL; } + if (ev->shiftKey) { m |= SAPP_MODIFIER_SHIFT; } + if (ev->altKey) { m |= SAPP_MODIFIER_ALT; } + if (ev->metaKey) { m |= SAPP_MODIFIER_SUPER; } + m |= _sapp_emsc_mouse_button_mods(_sapp.emsc.mouse_buttons); + return m; +} + +#if defined(SOKOL_WGPU) +_SOKOL_PRIVATE void _sapp_emsc_wgpu_size_changed(void); +#endif + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_size_changed(int event_type, const EmscriptenUiEvent* ui_event, void* user_data) { + _SOKOL_UNUSED(event_type); + _SOKOL_UNUSED(user_data); + double w, h; + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + /* The above method might report zero when toggling HTML5 fullscreen, + in that case use the window's inner width reported by the + emscripten event. This works ok when toggling *into* fullscreen + but doesn't properly restore the previous canvas size when switching + back from fullscreen. + + In general, due to the HTML5's fullscreen API's flaky nature it is + recommended to use 'soft fullscreen' (stretching the WebGL canvas + over the browser windows client rect) with a CSS definition like this: + + position: absolute; + top: 0px; + left: 0px; + margin: 0px; + border: 0; + width: 100%; + height: 100%; + overflow: hidden; + display: block; + */ + if (w < 1.0) { + w = ui_event->windowInnerWidth; + } + else { + _sapp.window_width = (int)roundf(w); + } + if (h < 1.0) { + h = ui_event->windowInnerHeight; + } + else { + _sapp.window_height = (int)roundf(h); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); + SOKOL_ASSERT((_sapp.framebuffer_width > 0) && (_sapp.framebuffer_height > 0)); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_WGPU) + // on WebGPU: recreate size-dependent rendering surfaces + _sapp_emsc_wgpu_size_changed(); + #endif + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_RESIZED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_mouse_cb(int emsc_type, const EmscriptenMouseEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool consume_event = !_sapp.desc.html5_bubble_mouse_events; + _sapp.emsc.mouse_buttons = emsc_event->buttons; + if (_sapp.mouse.locked) { + _sapp.mouse.dx = (float) emsc_event->movementX; + _sapp.mouse.dy = (float) emsc_event->movementY; + } else { + float new_x = emsc_event->targetX * _sapp.dpi_scale; + float new_y = emsc_event->targetY * _sapp.dpi_scale; + if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } + if (_sapp_events_enabled() && (emsc_event->button >= 0) && (emsc_event->button < SAPP_MAX_MOUSEBUTTONS)) { + sapp_event_type type; + bool is_button_event = false; + bool clear_dxdy = false; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_MOUSEDOWN: + type = SAPP_EVENTTYPE_MOUSE_DOWN; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + type = SAPP_EVENTTYPE_MOUSE_UP; + is_button_event = true; + break; + case EMSCRIPTEN_EVENT_MOUSEMOVE: + type = SAPP_EVENTTYPE_MOUSE_MOVE; + break; + case EMSCRIPTEN_EVENT_MOUSEENTER: + type = SAPP_EVENTTYPE_MOUSE_ENTER; + clear_dxdy = true; + break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: + type = SAPP_EVENTTYPE_MOUSE_LEAVE; + clear_dxdy = true; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(emsc_event); + if (is_button_event) { + switch (emsc_event->button) { + case 0: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_LEFT; break; + case 1: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_MIDDLE; break; + case 2: _sapp.event.mouse_button = SAPP_MOUSEBUTTON_RIGHT; break; + default: _sapp.event.mouse_button = (sapp_mousebutton)emsc_event->button; break; + } + } else { + _sapp.event.mouse_button = SAPP_MOUSEBUTTON_INVALID; + } + consume_event |= _sapp_call_event(&_sapp.event); + } + // mouse lock can only be activated in mouse button events (not in move, enter or leave) + if (is_button_event) { + _sapp_emsc_update_mouse_lock_state(); + } + } + return consume_event; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_wheel_cb(int emsc_type, const EmscriptenWheelEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(user_data); + bool consume_event = !_sapp.desc.html5_bubble_wheel_events; + _sapp.emsc.mouse_buttons = emsc_event->mouse.buttons; + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_emsc_mouse_event_mods(&emsc_event->mouse); + /* see https://github.com/floooh/sokol/issues/339 */ + float scale; + switch (emsc_event->deltaMode) { + case DOM_DELTA_PIXEL: scale = -0.04f; break; + case DOM_DELTA_LINE: scale = -1.33f; break; + case DOM_DELTA_PAGE: scale = -10.0f; break; // FIXME: this is a guess + default: scale = -0.1f; break; // shouldn't happen + } + _sapp.event.scroll_x = scale * (float)emsc_event->deltaX; + _sapp.event.scroll_y = scale * (float)emsc_event->deltaY; + consume_event |= _sapp_call_event(&_sapp.event); + } + _sapp_emsc_update_mouse_lock_state(); + return consume_event; +} + +static struct { + const char* str; + sapp_keycode code; +} _sapp_emsc_keymap[] = { + { "Backspace", SAPP_KEYCODE_BACKSPACE }, + { "Tab", SAPP_KEYCODE_TAB }, + { "Enter", SAPP_KEYCODE_ENTER }, + { "ShiftLeft", SAPP_KEYCODE_LEFT_SHIFT }, + { "ShiftRight", SAPP_KEYCODE_RIGHT_SHIFT }, + { "ControlLeft", SAPP_KEYCODE_LEFT_CONTROL }, + { "ControlRight", SAPP_KEYCODE_RIGHT_CONTROL }, + { "AltLeft", SAPP_KEYCODE_LEFT_ALT }, + { "AltRight", SAPP_KEYCODE_RIGHT_ALT }, + { "Pause", SAPP_KEYCODE_PAUSE }, + { "CapsLock", SAPP_KEYCODE_CAPS_LOCK }, + { "Escape", SAPP_KEYCODE_ESCAPE }, + { "Space", SAPP_KEYCODE_SPACE }, + { "PageUp", SAPP_KEYCODE_PAGE_UP }, + { "PageDown", SAPP_KEYCODE_PAGE_DOWN }, + { "End", SAPP_KEYCODE_END }, + { "Home", SAPP_KEYCODE_HOME }, + { "ArrowLeft", SAPP_KEYCODE_LEFT }, + { "ArrowUp", SAPP_KEYCODE_UP }, + { "ArrowRight", SAPP_KEYCODE_RIGHT }, + { "ArrowDown", SAPP_KEYCODE_DOWN }, + { "PrintScreen", SAPP_KEYCODE_PRINT_SCREEN }, + { "Insert", SAPP_KEYCODE_INSERT }, + { "Delete", SAPP_KEYCODE_DELETE }, + { "Digit0", SAPP_KEYCODE_0 }, + { "Digit1", SAPP_KEYCODE_1 }, + { "Digit2", SAPP_KEYCODE_2 }, + { "Digit3", SAPP_KEYCODE_3 }, + { "Digit4", SAPP_KEYCODE_4 }, + { "Digit5", SAPP_KEYCODE_5 }, + { "Digit6", SAPP_KEYCODE_6 }, + { "Digit7", SAPP_KEYCODE_7 }, + { "Digit8", SAPP_KEYCODE_8 }, + { "Digit9", SAPP_KEYCODE_9 }, + { "KeyA", SAPP_KEYCODE_A }, + { "KeyB", SAPP_KEYCODE_B }, + { "KeyC", SAPP_KEYCODE_C }, + { "KeyD", SAPP_KEYCODE_D }, + { "KeyE", SAPP_KEYCODE_E }, + { "KeyF", SAPP_KEYCODE_F }, + { "KeyG", SAPP_KEYCODE_G }, + { "KeyH", SAPP_KEYCODE_H }, + { "KeyI", SAPP_KEYCODE_I }, + { "KeyJ", SAPP_KEYCODE_J }, + { "KeyK", SAPP_KEYCODE_K }, + { "KeyL", SAPP_KEYCODE_L }, + { "KeyM", SAPP_KEYCODE_M }, + { "KeyN", SAPP_KEYCODE_N }, + { "KeyO", SAPP_KEYCODE_O }, + { "KeyP", SAPP_KEYCODE_P }, + { "KeyQ", SAPP_KEYCODE_Q }, + { "KeyR", SAPP_KEYCODE_R }, + { "KeyS", SAPP_KEYCODE_S }, + { "KeyT", SAPP_KEYCODE_T }, + { "KeyU", SAPP_KEYCODE_U }, + { "KeyV", SAPP_KEYCODE_V }, + { "KeyW", SAPP_KEYCODE_W }, + { "KeyX", SAPP_KEYCODE_X }, + { "KeyY", SAPP_KEYCODE_Y }, + { "KeyZ", SAPP_KEYCODE_Z }, + { "MetaLeft", SAPP_KEYCODE_LEFT_SUPER }, + { "MetaRight", SAPP_KEYCODE_RIGHT_SUPER }, + { "Numpad0", SAPP_KEYCODE_KP_0 }, + { "Numpad1", SAPP_KEYCODE_KP_1 }, + { "Numpad2", SAPP_KEYCODE_KP_2 }, + { "Numpad3", SAPP_KEYCODE_KP_3 }, + { "Numpad4", SAPP_KEYCODE_KP_4 }, + { "Numpad5", SAPP_KEYCODE_KP_5 }, + { "Numpad6", SAPP_KEYCODE_KP_6 }, + { "Numpad7", SAPP_KEYCODE_KP_7 }, + { "Numpad8", SAPP_KEYCODE_KP_8 }, + { "Numpad9", SAPP_KEYCODE_KP_9 }, + { "NumpadMultiply", SAPP_KEYCODE_KP_MULTIPLY }, + { "NumpadAdd", SAPP_KEYCODE_KP_ADD }, + { "NumpadSubtract", SAPP_KEYCODE_KP_SUBTRACT }, + { "NumpadDecimal", SAPP_KEYCODE_KP_DECIMAL }, + { "NumpadDivide", SAPP_KEYCODE_KP_DIVIDE }, + { "F1", SAPP_KEYCODE_F1 }, + { "F2", SAPP_KEYCODE_F2 }, + { "F3", SAPP_KEYCODE_F3 }, + { "F4", SAPP_KEYCODE_F4 }, + { "F5", SAPP_KEYCODE_F5 }, + { "F6", SAPP_KEYCODE_F6 }, + { "F7", SAPP_KEYCODE_F7 }, + { "F8", SAPP_KEYCODE_F8 }, + { "F9", SAPP_KEYCODE_F9 }, + { "F10", SAPP_KEYCODE_F10 }, + { "F11", SAPP_KEYCODE_F11 }, + { "F12", SAPP_KEYCODE_F12 }, + { "NumLock", SAPP_KEYCODE_NUM_LOCK }, + { "ScrollLock", SAPP_KEYCODE_SCROLL_LOCK }, + { "Semicolon", SAPP_KEYCODE_SEMICOLON }, + { "Equal", SAPP_KEYCODE_EQUAL }, + { "Comma", SAPP_KEYCODE_COMMA }, + { "Minus", SAPP_KEYCODE_MINUS }, + { "Period", SAPP_KEYCODE_PERIOD }, + { "Slash", SAPP_KEYCODE_SLASH }, + { "Backquote", SAPP_KEYCODE_GRAVE_ACCENT }, + { "BracketLeft", SAPP_KEYCODE_LEFT_BRACKET }, + { "Backslash", SAPP_KEYCODE_BACKSLASH }, + { "BracketRight", SAPP_KEYCODE_RIGHT_BRACKET }, + { "Quote", SAPP_KEYCODE_GRAVE_ACCENT }, // FIXME: ??? + { 0, SAPP_KEYCODE_INVALID }, +}; + +_SOKOL_PRIVATE sapp_keycode _sapp_emsc_translate_key(const char* str) { + int i = 0; + const char* keystr; + while (( keystr = _sapp_emsc_keymap[i].str )) { + if (0 == strcmp(str, keystr)) { + return _sapp_emsc_keymap[i].code; + } + i += 1; + } + return SAPP_KEYCODE_INVALID; +} + +// returns true if the key code is a 'character key', this is used to decide +// if a key event needs to bubble up to create a char event +_SOKOL_PRIVATE bool _sapp_emsc_is_char_key(sapp_keycode key_code) { + return key_code < SAPP_KEYCODE_WORLD_1; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_key_cb(int emsc_type, const EmscriptenKeyboardEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool consume_event = false; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_KEYDOWN: + type = SAPP_EVENTTYPE_KEY_DOWN; + break; + case EMSCRIPTEN_EVENT_KEYUP: + type = SAPP_EVENTTYPE_KEY_UP; + break; + case EMSCRIPTEN_EVENT_KEYPRESS: + type = SAPP_EVENTTYPE_CHAR; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + bool send_keyup_followup = false; + _sapp_init_event(type); + _sapp.event.key_repeat = emsc_event->repeat; + _sapp.event.modifiers = _sapp_emsc_key_event_mods(emsc_event); + if (type == SAPP_EVENTTYPE_CHAR) { + // NOTE: charCode doesn't appear to be supported on Android Chrome + _sapp.event.char_code = emsc_event->charCode; + consume_event |= !_sapp.desc.html5_bubble_char_events; + } else { + if (0 != emsc_event->code[0]) { + // This code path is for desktop browsers which send untranslated 'physical' key code strings + // (which is what we actually want for key events) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->code); + } else { + // This code path is for mobile browsers which only send localized key code + // strings. Note that the translation will only work for a small subset + // of localization-agnostic keys (like Enter, arrow keys, etc...), but + // regular alpha-numeric keys will all result in an SAPP_KEYCODE_INVALID) + _sapp.event.key_code = _sapp_emsc_translate_key(emsc_event->key); + } + + // Special hack for macOS: if the Super key is pressed, macOS doesn't + // send keyUp events. As a workaround, to prevent keys from + // "sticking", we'll send a keyup event following a keydown + // when the SUPER key is pressed + if ((type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.key_code != SAPP_KEYCODE_LEFT_SUPER) && + (_sapp.event.key_code != SAPP_KEYCODE_RIGHT_SUPER) && + (_sapp.event.modifiers & SAPP_MODIFIER_SUPER)) + { + send_keyup_followup = true; + } + + // 'character key events' will always need to bubble up, otherwise the browser + // wouldn't be able to generate character events. + if (!_sapp_emsc_is_char_key(_sapp.event.key_code)) { + consume_event |= !_sapp.desc.html5_bubble_key_events; + } + } + consume_event |= _sapp_call_event(&_sapp.event); + if (send_keyup_followup) { + _sapp.event.type = SAPP_EVENTTYPE_KEY_UP; + consume_event |= _sapp_call_event(&_sapp.event); + } + } + } + _sapp_emsc_update_mouse_lock_state(); + return consume_event; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_touch_cb(int emsc_type, const EmscriptenTouchEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(user_data); + bool consume_event = !_sapp.desc.html5_bubble_touch_events; + if (_sapp_events_enabled()) { + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + type = SAPP_EVENTTYPE_INVALID; + break; + } + if (type != SAPP_EVENTTYPE_INVALID) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_emsc_touch_event_mods(emsc_event); + _sapp.event.num_touches = emsc_event->numTouches; + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int i = 0; i < _sapp.event.num_touches; i++) { + const EmscriptenTouchPoint* src = &emsc_event->touches[i]; + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)src->identifier; + dst->pos_x = src->targetX * _sapp.dpi_scale; + dst->pos_y = src->targetY * _sapp.dpi_scale; + dst->changed = src->isChanged; + } + consume_event |= _sapp_call_event(&_sapp.event); + } + } + return consume_event; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_focus_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(emsc_event); + _SOKOL_UNUSED(user_data); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FOCUSED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_blur_cb(int emsc_type, const EmscriptenFocusEvent* emsc_event, void* user_data) { + _SOKOL_UNUSED(emsc_type); + _SOKOL_UNUSED(emsc_event); + _SOKOL_UNUSED(user_data); + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_UNFOCUSED); + _sapp_call_event(&_sapp.event); + } + return true; +} + +#if defined(SOKOL_GLES3) +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_webgl_context_cb(int emsc_type, const void* reserved, void* user_data) { + _SOKOL_UNUSED(reserved); + _SOKOL_UNUSED(user_data); + sapp_event_type type; + switch (emsc_type) { + case EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: type = SAPP_EVENTTYPE_SUSPENDED; break; + case EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: type = SAPP_EVENTTYPE_RESUMED; break; + default: type = SAPP_EVENTTYPE_INVALID; break; + } + if (_sapp_events_enabled() && (SAPP_EVENTTYPE_INVALID != type)) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } + return true; +} + +_SOKOL_PRIVATE void _sapp_emsc_webgl_init(void) { + EmscriptenWebGLContextAttributes attrs; + emscripten_webgl_init_context_attributes(&attrs); + attrs.alpha = _sapp.desc.alpha; + attrs.depth = true; + attrs.stencil = true; + attrs.antialias = _sapp.sample_count > 1; + attrs.premultipliedAlpha = _sapp.desc.html5_premultiplied_alpha; + attrs.preserveDrawingBuffer = _sapp.desc.html5_preserve_drawing_buffer; + attrs.enableExtensionsByDefault = true; + attrs.majorVersion = 2; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(_sapp.html5_canvas_selector, &attrs); + // FIXME: error message? + emscripten_webgl_make_context_current(ctx); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); +} +#endif + +#if defined(SOKOL_WGPU) + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_create_swapchain(void) { + SOKOL_ASSERT(_sapp.wgpu.instance); + SOKOL_ASSERT(_sapp.wgpu.device); + SOKOL_ASSERT(0 == _sapp.wgpu.surface); + SOKOL_ASSERT(0 == _sapp.wgpu.swapchain); + SOKOL_ASSERT(0 == _sapp.wgpu.msaa_tex); + SOKOL_ASSERT(0 == _sapp.wgpu.msaa_view); + SOKOL_ASSERT(0 == _sapp.wgpu.depth_stencil_tex); + SOKOL_ASSERT(0 == _sapp.wgpu.depth_stencil_view); + SOKOL_ASSERT(0 == _sapp.wgpu.swapchain_view); + + WGPUSurfaceDescriptorFromCanvasHTMLSelector canvas_desc; + _sapp_clear(&canvas_desc, sizeof(canvas_desc)); + canvas_desc.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + canvas_desc.selector = _sapp.html5_canvas_selector; + WGPUSurfaceDescriptor surf_desc; + _sapp_clear(&surf_desc, sizeof(surf_desc)); + surf_desc.nextInChain = &canvas_desc.chain; + _sapp.wgpu.surface = wgpuInstanceCreateSurface(_sapp.wgpu.instance, &surf_desc); + if (0 == _sapp.wgpu.surface) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_SURFACE_FAILED); + } + _sapp.wgpu.render_format = wgpuSurfaceGetPreferredFormat(_sapp.wgpu.surface, _sapp.wgpu.adapter); + + WGPUSwapChainDescriptor sc_desc; + _sapp_clear(&sc_desc, sizeof(sc_desc)); + sc_desc.usage = WGPUTextureUsage_RenderAttachment; + sc_desc.format = _sapp.wgpu.render_format; + sc_desc.width = (uint32_t)_sapp.framebuffer_width; + sc_desc.height = (uint32_t)_sapp.framebuffer_height; + sc_desc.presentMode = WGPUPresentMode_Fifo; + _sapp.wgpu.swapchain = wgpuDeviceCreateSwapChain(_sapp.wgpu.device, _sapp.wgpu.surface, &sc_desc); + if (0 == _sapp.wgpu.swapchain) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_SWAPCHAIN_FAILED); + } + + WGPUTextureDescriptor ds_desc; + _sapp_clear(&ds_desc, sizeof(ds_desc)); + ds_desc.usage = WGPUTextureUsage_RenderAttachment; + ds_desc.dimension = WGPUTextureDimension_2D; + ds_desc.size.width = (uint32_t)_sapp.framebuffer_width; + ds_desc.size.height = (uint32_t)_sapp.framebuffer_height; + ds_desc.size.depthOrArrayLayers = 1; + ds_desc.format = WGPUTextureFormat_Depth32FloatStencil8; + ds_desc.mipLevelCount = 1; + ds_desc.sampleCount = (uint32_t)_sapp.sample_count; + _sapp.wgpu.depth_stencil_tex = wgpuDeviceCreateTexture(_sapp.wgpu.device, &ds_desc); + if (0 == _sapp.wgpu.depth_stencil_tex) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_TEXTURE_FAILED); + } + _sapp.wgpu.depth_stencil_view = wgpuTextureCreateView(_sapp.wgpu.depth_stencil_tex, 0); + if (0 == _sapp.wgpu.depth_stencil_view) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_DEPTH_STENCIL_VIEW_FAILED); + } + + if (_sapp.sample_count > 1) { + WGPUTextureDescriptor msaa_desc; + _sapp_clear(&msaa_desc, sizeof(msaa_desc)); + msaa_desc.usage = WGPUTextureUsage_RenderAttachment; + msaa_desc.dimension = WGPUTextureDimension_2D; + msaa_desc.size.width = (uint32_t)_sapp.framebuffer_width; + msaa_desc.size.height = (uint32_t)_sapp.framebuffer_height; + msaa_desc.size.depthOrArrayLayers = 1; + msaa_desc.format = _sapp.wgpu.render_format; + msaa_desc.mipLevelCount = 1; + msaa_desc.sampleCount = (uint32_t)_sapp.sample_count; + _sapp.wgpu.msaa_tex = wgpuDeviceCreateTexture(_sapp.wgpu.device, &msaa_desc); + if (0 == _sapp.wgpu.msaa_tex) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_MSAA_TEXTURE_FAILED); + } + _sapp.wgpu.msaa_view = wgpuTextureCreateView(_sapp.wgpu.msaa_tex, 0); + if (0 == _sapp.wgpu.msaa_view) { + _SAPP_PANIC(WGPU_SWAPCHAIN_CREATE_MSAA_VIEW_FAILED); + } + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_discard_swapchain(void) { + if (_sapp.wgpu.msaa_view) { + wgpuTextureViewRelease(_sapp.wgpu.msaa_view); + _sapp.wgpu.msaa_view = 0; + } + if (_sapp.wgpu.msaa_tex) { + wgpuTextureRelease(_sapp.wgpu.msaa_tex); + _sapp.wgpu.msaa_tex = 0; + } + if (_sapp.wgpu.depth_stencil_view) { + wgpuTextureViewRelease(_sapp.wgpu.depth_stencil_view); + _sapp.wgpu.depth_stencil_view = 0; + } + if (_sapp.wgpu.depth_stencil_tex) { + wgpuTextureRelease(_sapp.wgpu.depth_stencil_tex); + _sapp.wgpu.depth_stencil_tex = 0; + } + if (_sapp.wgpu.swapchain) { + wgpuSwapChainRelease(_sapp.wgpu.swapchain); + _sapp.wgpu.swapchain = 0; + } + if (_sapp.wgpu.surface) { + wgpuSurfaceRelease(_sapp.wgpu.surface); + _sapp.wgpu.surface = 0; + } +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_size_changed(void) { + _sapp_emsc_wgpu_discard_swapchain(); + _sapp_emsc_wgpu_create_swapchain(); +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_request_device_cb(WGPURequestDeviceStatus status, WGPUDevice device, const char* msg, void* userdata) { + _SOKOL_UNUSED(msg); + _SOKOL_UNUSED(userdata); + SOKOL_ASSERT(!_sapp.wgpu.async_init_done); + if (status != WGPURequestDeviceStatus_Success) { + if (status == WGPURequestDeviceStatus_Error) { + _SAPP_PANIC(WGPU_REQUEST_DEVICE_STATUS_ERROR); + } else { + _SAPP_PANIC(WGPU_REQUEST_DEVICE_STATUS_UNKNOWN); + } + } + SOKOL_ASSERT(device); + _sapp.wgpu.device = device; + _sapp_emsc_wgpu_create_swapchain(); + _sapp.wgpu.async_init_done = true; +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_request_adapter_cb(WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* msg, void* userdata) { + _SOKOL_UNUSED(msg); + _SOKOL_UNUSED(userdata); + if (status != WGPURequestAdapterStatus_Success) { + switch (status) { + case WGPURequestAdapterStatus_Unavailable: _SAPP_PANIC(WGPU_REQUEST_ADAPTER_STATUS_UNAVAILABLE); break; + case WGPURequestAdapterStatus_Error: _SAPP_PANIC(WGPU_REQUEST_ADAPTER_STATUS_ERROR); break; + default: _SAPP_PANIC(WGPU_REQUEST_ADAPTER_STATUS_UNKNOWN); break; + } + } + SOKOL_ASSERT(adapter); + _sapp.wgpu.adapter = adapter; + size_t cur_feature_index = 1; + #define _SAPP_WGPU_MAX_REQUESTED_FEATURES (8) + WGPUFeatureName requiredFeatures[_SAPP_WGPU_MAX_REQUESTED_FEATURES] = { + WGPUFeatureName_Depth32FloatStencil8, + }; + // check for optional features we're interested in + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionBC)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionBC; + } + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionETC2)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionETC2; + } + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionASTC)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionASTC; + } + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_Float32Filterable)) { + SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES); + requiredFeatures[cur_feature_index++] = WGPUFeatureName_Float32Filterable; + } + #undef _SAPP_WGPU_MAX_REQUESTED_FEATURES + + WGPUDeviceDescriptor dev_desc; + _sapp_clear(&dev_desc, sizeof(dev_desc)); + dev_desc.requiredFeatureCount = cur_feature_index; + dev_desc.requiredFeatures = requiredFeatures, + wgpuAdapterRequestDevice(adapter, &dev_desc, _sapp_emsc_wgpu_request_device_cb, 0); +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_init(void) { + SOKOL_ASSERT(0 == _sapp.wgpu.instance); + SOKOL_ASSERT(!_sapp.wgpu.async_init_done); + _sapp.wgpu.instance = wgpuCreateInstance(0); + if (0 == _sapp.wgpu.instance) { + _SAPP_PANIC(WGPU_CREATE_INSTANCE_FAILED); + } + // FIXME: power preference? + wgpuInstanceRequestAdapter(_sapp.wgpu.instance, 0, _sapp_emsc_wgpu_request_adapter_cb, 0); +} + +_SOKOL_PRIVATE void _sapp_emsc_wgpu_frame(void) { + if (_sapp.wgpu.async_init_done) { + _sapp.wgpu.swapchain_view = wgpuSwapChainGetCurrentTextureView(_sapp.wgpu.swapchain); + _sapp_frame(); + wgpuTextureViewRelease(_sapp.wgpu.swapchain_view); + _sapp.wgpu.swapchain_view = 0; + } +} +#endif // SOKOL_WGPU + +_SOKOL_PRIVATE void _sapp_emsc_register_eventhandlers(void) { + // NOTE: HTML canvas doesn't receive input focus, this is why key event handlers are added + // to the window object (this could be worked around by adding a "tab index" to the + // canvas) + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_mouse_cb); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_wheel_cb); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_key_cb); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_touch_cb); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockchange_cb); + emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, _sapp_emsc_pointerlockerror_cb); + emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_focus_cb); + emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, _sapp_emsc_blur_cb); + sapp_js_add_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_add_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_add_dragndrop_listeners(); + } + #if defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, _sapp_emsc_webgl_context_cb); + #endif +} + +_SOKOL_PRIVATE void _sapp_emsc_unregister_eventhandlers(void) { + emscripten_set_mousedown_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseup_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mousemove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseenter_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_mouseleave_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_wheel_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_touchstart_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchmove_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchend_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_touchcancel_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); + emscripten_set_pointerlockerror_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, 0, true, 0); + emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + if (!_sapp.desc.html5_canvas_resize) { + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, true, 0); + } + sapp_js_remove_beforeunload_listener(); + if (_sapp.clipboard.enabled) { + sapp_js_remove_clipboard_listener(); + } + if (_sapp.drop.enabled) { + sapp_js_remove_dragndrop_listeners(); + } + #if defined(SOKOL_GLES3) + emscripten_set_webglcontextlost_callback(_sapp.html5_canvas_selector, 0, true, 0); + emscripten_set_webglcontextrestored_callback(_sapp.html5_canvas_selector, 0, true, 0); + #endif +} + +_SOKOL_PRIVATE EM_BOOL _sapp_emsc_frame_animation_loop(double time, void* userData) { + _SOKOL_UNUSED(userData); + _sapp_timing_external(&_sapp.timing, time / 1000.0); + + #if defined(SOKOL_WGPU) + _sapp_emsc_wgpu_frame(); + #else + _sapp_frame(); + #endif + + // quit-handling + if (_sapp.quit_requested) { + _sapp_init_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + _sapp_call_event(&_sapp.event); + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + _sapp_emsc_unregister_eventhandlers(); + _sapp_call_cleanup(); + _sapp_discard_state(); + return EM_FALSE; + } + return EM_TRUE; +} + +_SOKOL_PRIVATE void _sapp_emsc_frame_main_loop(void) { + const double time = emscripten_performance_now(); + if (!_sapp_emsc_frame_animation_loop(time, 0)) { + emscripten_cancel_main_loop(); + } +} + +_SOKOL_PRIVATE void _sapp_emsc_run(const sapp_desc* desc) { + _sapp_init_state(desc); + const char* document_title = desc->html5_update_document_title ? _sapp.window_title : 0; + sapp_js_init(_sapp.html5_canvas_selector, document_title); + double w, h; + if (_sapp.desc.html5_canvas_resize) { + w = (double) _sapp_def(_sapp.desc.width, _SAPP_FALLBACK_DEFAULT_WINDOW_WIDTH); + h = (double) _sapp_def(_sapp.desc.height, _SAPP_FALLBACK_DEFAULT_WINDOW_HEIGHT); + } + else { + emscripten_get_element_css_size(_sapp.html5_canvas_selector, &w, &h); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, false, _sapp_emsc_size_changed); + } + if (_sapp.desc.high_dpi) { + _sapp.dpi_scale = emscripten_get_device_pixel_ratio(); + } + _sapp.window_width = (int)roundf(w); + _sapp.window_height = (int)roundf(h); + _sapp.framebuffer_width = (int)roundf(w * _sapp.dpi_scale); + _sapp.framebuffer_height = (int)roundf(h * _sapp.dpi_scale); + emscripten_set_canvas_element_size(_sapp.html5_canvas_selector, _sapp.framebuffer_width, _sapp.framebuffer_height); + #if defined(SOKOL_GLES3) + _sapp_emsc_webgl_init(); + #elif defined(SOKOL_WGPU) + _sapp_emsc_wgpu_init(); + #endif + _sapp.valid = true; + _sapp_emsc_register_eventhandlers(); + sapp_set_icon(&desc->icon); + + // start the frame loop + if (_sapp.desc.html5_use_emsc_set_main_loop) { + emscripten_set_main_loop(_sapp_emsc_frame_main_loop, 0, _sapp.desc.html5_emsc_set_main_loop_simulate_infinite_loop); + } else { + emscripten_request_animation_frame_loop(_sapp_emsc_frame_animation_loop, 0); + } + // NOT A BUG: do not call _sapp_discard_state() here, instead this is + // called in _sapp_emsc_frame() when the application is ordered to quit +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_emsc_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_EMSCRIPTEN */ + +// ██████ ██ ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ███ ██ ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>gl helpers +#if defined(SOKOL_GLCORE) +typedef struct { + int red_bits; + int green_bits; + int blue_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + int samples; + bool doublebuffer; + uintptr_t handle; +} _sapp_gl_fbconfig; + +_SOKOL_PRIVATE void _sapp_gl_init_fbconfig(_sapp_gl_fbconfig* fbconfig) { + _sapp_clear(fbconfig, sizeof(_sapp_gl_fbconfig)); + /* -1 means "don't care" */ + fbconfig->red_bits = -1; + fbconfig->green_bits = -1; + fbconfig->blue_bits = -1; + fbconfig->alpha_bits = -1; + fbconfig->depth_bits = -1; + fbconfig->stencil_bits = -1; + fbconfig->samples = -1; +} + +typedef struct { + int least_missing; + int least_color_diff; + int least_extra_diff; + bool best_match; +} _sapp_gl_fbselect; + +_SOKOL_PRIVATE void _sapp_gl_init_fbselect(_sapp_gl_fbselect* fbselect) { + _sapp_clear(fbselect, sizeof(_sapp_gl_fbselect)); + fbselect->least_missing = 1000000; + fbselect->least_color_diff = 10000000; + fbselect->least_extra_diff = 10000000; + fbselect->best_match = false; +} + +// NOTE: this is used only in the WGL code path +_SOKOL_PRIVATE bool _sapp_gl_select_fbconfig(_sapp_gl_fbselect* fbselect, const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* current) { + int missing = 0; + if (desired->doublebuffer != current->doublebuffer) { + return false; + } + + if ((desired->alpha_bits > 0) && (current->alpha_bits == 0)) { + missing++; + } + if ((desired->depth_bits > 0) && (current->depth_bits == 0)) { + missing++; + } + if ((desired->stencil_bits > 0) && (current->stencil_bits == 0)) { + missing++; + } + if ((desired->samples > 0) && (current->samples == 0)) { + /* Technically, several multisampling buffers could be + involved, but that's a lower level implementation detail and + not important to us here, so we count them as one + */ + missing++; + } + + /* These polynomials make many small channel size differences matter + less than one large channel size difference + Calculate color channel size difference value + */ + int color_diff = 0; + if (desired->red_bits != -1) { + color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); + } + if (desired->green_bits != -1) { + color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); + } + if (desired->blue_bits != -1) { + color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); + } + + /* Calculate non-color channel size difference value */ + int extra_diff = 0; + if (desired->alpha_bits != -1) { + extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); + } + if (desired->depth_bits != -1) { + extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); + } + if (desired->stencil_bits != -1) { + extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); + } + if (desired->samples != -1) { + extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); + } + + /* Figure out if the current one is better than the best one found so far + Least number of missing buffers is the most important heuristic, + then color buffer size match and lastly size match for other buffers + */ + bool new_closest = false; + if (missing < fbselect->least_missing) { + new_closest = true; + } else if (missing == fbselect->least_missing) { + if ((color_diff < fbselect->least_color_diff) || + ((color_diff == fbselect->least_color_diff) && (extra_diff < fbselect->least_extra_diff))) + { + new_closest = true; + } + } + if (new_closest) { + fbselect->least_missing = missing; + fbselect->least_color_diff = color_diff; + fbselect->least_extra_diff = extra_diff; + fbselect->best_match = (missing | color_diff | extra_diff) == 0; + } + return new_closest; +} + +// NOTE: this is used only in the GLX code path +_SOKOL_PRIVATE const _sapp_gl_fbconfig* _sapp_gl_choose_fbconfig(const _sapp_gl_fbconfig* desired, const _sapp_gl_fbconfig* alternatives, int count) { + int missing, least_missing = 1000000; + int color_diff, least_color_diff = 10000000; + int extra_diff, least_extra_diff = 10000000; + const _sapp_gl_fbconfig* current; + const _sapp_gl_fbconfig* closest = 0; + for (int i = 0; i < count; i++) { + current = alternatives + i; + if (desired->doublebuffer != current->doublebuffer) { + continue; + } + missing = 0; + if (desired->alpha_bits > 0 && current->alpha_bits == 0) { + missing++; + } + if (desired->depth_bits > 0 && current->depth_bits == 0) { + missing++; + } + if (desired->stencil_bits > 0 && current->stencil_bits == 0) { + missing++; + } + if (desired->samples > 0 && current->samples == 0) { + /* Technically, several multisampling buffers could be + involved, but that's a lower level implementation detail and + not important to us here, so we count them as one + */ + missing++; + } + + /* These polynomials make many small channel size differences matter + less than one large channel size difference + Calculate color channel size difference value + */ + color_diff = 0; + if (desired->red_bits != -1) { + color_diff += (desired->red_bits - current->red_bits) * (desired->red_bits - current->red_bits); + } + if (desired->green_bits != -1) { + color_diff += (desired->green_bits - current->green_bits) * (desired->green_bits - current->green_bits); + } + if (desired->blue_bits != -1) { + color_diff += (desired->blue_bits - current->blue_bits) * (desired->blue_bits - current->blue_bits); + } + + /* Calculate non-color channel size difference value */ + extra_diff = 0; + if (desired->alpha_bits != -1) { + extra_diff += (desired->alpha_bits - current->alpha_bits) * (desired->alpha_bits - current->alpha_bits); + } + if (desired->depth_bits != -1) { + extra_diff += (desired->depth_bits - current->depth_bits) * (desired->depth_bits - current->depth_bits); + } + if (desired->stencil_bits != -1) { + extra_diff += (desired->stencil_bits - current->stencil_bits) * (desired->stencil_bits - current->stencil_bits); + } + if (desired->samples != -1) { + extra_diff += (desired->samples - current->samples) * (desired->samples - current->samples); + } + + /* Figure out if the current one is better than the best one found so far + Least number of missing buffers is the most important heuristic, + then color buffer size match and lastly size match for other buffers + */ + if (missing < least_missing) { + closest = current; + } + else if (missing == least_missing) { + if ((color_diff < least_color_diff) || + (color_diff == least_color_diff && extra_diff < least_extra_diff)) + { + closest = current; + } + } + if (current == closest) { + least_missing = missing; + least_color_diff = color_diff; + least_extra_diff = extra_diff; + } + } + return closest; +} +#endif + +// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ +// +// >>windows +#if defined(_SAPP_WIN32) +_SOKOL_PRIVATE bool _sapp_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sapp_clear(dst, (size_t)dst_num_bytes); + const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); + const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); + if ((dst_needed > 0) && (dst_needed < dst_chars)) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); + return true; + } + else { + /* input string doesn't fit into destination buffer */ + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_keytable(void) { + /* same as GLFW */ + _sapp.keycodes[0x00B] = SAPP_KEYCODE_0; + _sapp.keycodes[0x002] = SAPP_KEYCODE_1; + _sapp.keycodes[0x003] = SAPP_KEYCODE_2; + _sapp.keycodes[0x004] = SAPP_KEYCODE_3; + _sapp.keycodes[0x005] = SAPP_KEYCODE_4; + _sapp.keycodes[0x006] = SAPP_KEYCODE_5; + _sapp.keycodes[0x007] = SAPP_KEYCODE_6; + _sapp.keycodes[0x008] = SAPP_KEYCODE_7; + _sapp.keycodes[0x009] = SAPP_KEYCODE_8; + _sapp.keycodes[0x00A] = SAPP_KEYCODE_9; + _sapp.keycodes[0x01E] = SAPP_KEYCODE_A; + _sapp.keycodes[0x030] = SAPP_KEYCODE_B; + _sapp.keycodes[0x02E] = SAPP_KEYCODE_C; + _sapp.keycodes[0x020] = SAPP_KEYCODE_D; + _sapp.keycodes[0x012] = SAPP_KEYCODE_E; + _sapp.keycodes[0x021] = SAPP_KEYCODE_F; + _sapp.keycodes[0x022] = SAPP_KEYCODE_G; + _sapp.keycodes[0x023] = SAPP_KEYCODE_H; + _sapp.keycodes[0x017] = SAPP_KEYCODE_I; + _sapp.keycodes[0x024] = SAPP_KEYCODE_J; + _sapp.keycodes[0x025] = SAPP_KEYCODE_K; + _sapp.keycodes[0x026] = SAPP_KEYCODE_L; + _sapp.keycodes[0x032] = SAPP_KEYCODE_M; + _sapp.keycodes[0x031] = SAPP_KEYCODE_N; + _sapp.keycodes[0x018] = SAPP_KEYCODE_O; + _sapp.keycodes[0x019] = SAPP_KEYCODE_P; + _sapp.keycodes[0x010] = SAPP_KEYCODE_Q; + _sapp.keycodes[0x013] = SAPP_KEYCODE_R; + _sapp.keycodes[0x01F] = SAPP_KEYCODE_S; + _sapp.keycodes[0x014] = SAPP_KEYCODE_T; + _sapp.keycodes[0x016] = SAPP_KEYCODE_U; + _sapp.keycodes[0x02F] = SAPP_KEYCODE_V; + _sapp.keycodes[0x011] = SAPP_KEYCODE_W; + _sapp.keycodes[0x02D] = SAPP_KEYCODE_X; + _sapp.keycodes[0x015] = SAPP_KEYCODE_Y; + _sapp.keycodes[0x02C] = SAPP_KEYCODE_Z; + _sapp.keycodes[0x028] = SAPP_KEYCODE_APOSTROPHE; + _sapp.keycodes[0x02B] = SAPP_KEYCODE_BACKSLASH; + _sapp.keycodes[0x033] = SAPP_KEYCODE_COMMA; + _sapp.keycodes[0x00D] = SAPP_KEYCODE_EQUAL; + _sapp.keycodes[0x029] = SAPP_KEYCODE_GRAVE_ACCENT; + _sapp.keycodes[0x01A] = SAPP_KEYCODE_LEFT_BRACKET; + _sapp.keycodes[0x00C] = SAPP_KEYCODE_MINUS; + _sapp.keycodes[0x034] = SAPP_KEYCODE_PERIOD; + _sapp.keycodes[0x01B] = SAPP_KEYCODE_RIGHT_BRACKET; + _sapp.keycodes[0x027] = SAPP_KEYCODE_SEMICOLON; + _sapp.keycodes[0x035] = SAPP_KEYCODE_SLASH; + _sapp.keycodes[0x056] = SAPP_KEYCODE_WORLD_2; + _sapp.keycodes[0x00E] = SAPP_KEYCODE_BACKSPACE; + _sapp.keycodes[0x153] = SAPP_KEYCODE_DELETE; + _sapp.keycodes[0x14F] = SAPP_KEYCODE_END; + _sapp.keycodes[0x01C] = SAPP_KEYCODE_ENTER; + _sapp.keycodes[0x001] = SAPP_KEYCODE_ESCAPE; + _sapp.keycodes[0x147] = SAPP_KEYCODE_HOME; + _sapp.keycodes[0x152] = SAPP_KEYCODE_INSERT; + _sapp.keycodes[0x15D] = SAPP_KEYCODE_MENU; + _sapp.keycodes[0x151] = SAPP_KEYCODE_PAGE_DOWN; + _sapp.keycodes[0x149] = SAPP_KEYCODE_PAGE_UP; + _sapp.keycodes[0x045] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x146] = SAPP_KEYCODE_PAUSE; + _sapp.keycodes[0x039] = SAPP_KEYCODE_SPACE; + _sapp.keycodes[0x00F] = SAPP_KEYCODE_TAB; + _sapp.keycodes[0x03A] = SAPP_KEYCODE_CAPS_LOCK; + _sapp.keycodes[0x145] = SAPP_KEYCODE_NUM_LOCK; + _sapp.keycodes[0x046] = SAPP_KEYCODE_SCROLL_LOCK; + _sapp.keycodes[0x03B] = SAPP_KEYCODE_F1; + _sapp.keycodes[0x03C] = SAPP_KEYCODE_F2; + _sapp.keycodes[0x03D] = SAPP_KEYCODE_F3; + _sapp.keycodes[0x03E] = SAPP_KEYCODE_F4; + _sapp.keycodes[0x03F] = SAPP_KEYCODE_F5; + _sapp.keycodes[0x040] = SAPP_KEYCODE_F6; + _sapp.keycodes[0x041] = SAPP_KEYCODE_F7; + _sapp.keycodes[0x042] = SAPP_KEYCODE_F8; + _sapp.keycodes[0x043] = SAPP_KEYCODE_F9; + _sapp.keycodes[0x044] = SAPP_KEYCODE_F10; + _sapp.keycodes[0x057] = SAPP_KEYCODE_F11; + _sapp.keycodes[0x058] = SAPP_KEYCODE_F12; + _sapp.keycodes[0x064] = SAPP_KEYCODE_F13; + _sapp.keycodes[0x065] = SAPP_KEYCODE_F14; + _sapp.keycodes[0x066] = SAPP_KEYCODE_F15; + _sapp.keycodes[0x067] = SAPP_KEYCODE_F16; + _sapp.keycodes[0x068] = SAPP_KEYCODE_F17; + _sapp.keycodes[0x069] = SAPP_KEYCODE_F18; + _sapp.keycodes[0x06A] = SAPP_KEYCODE_F19; + _sapp.keycodes[0x06B] = SAPP_KEYCODE_F20; + _sapp.keycodes[0x06C] = SAPP_KEYCODE_F21; + _sapp.keycodes[0x06D] = SAPP_KEYCODE_F22; + _sapp.keycodes[0x06E] = SAPP_KEYCODE_F23; + _sapp.keycodes[0x076] = SAPP_KEYCODE_F24; + _sapp.keycodes[0x038] = SAPP_KEYCODE_LEFT_ALT; + _sapp.keycodes[0x01D] = SAPP_KEYCODE_LEFT_CONTROL; + _sapp.keycodes[0x02A] = SAPP_KEYCODE_LEFT_SHIFT; + _sapp.keycodes[0x15B] = SAPP_KEYCODE_LEFT_SUPER; + _sapp.keycodes[0x137] = SAPP_KEYCODE_PRINT_SCREEN; + _sapp.keycodes[0x138] = SAPP_KEYCODE_RIGHT_ALT; + _sapp.keycodes[0x11D] = SAPP_KEYCODE_RIGHT_CONTROL; + _sapp.keycodes[0x036] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x136] = SAPP_KEYCODE_RIGHT_SHIFT; + _sapp.keycodes[0x15C] = SAPP_KEYCODE_RIGHT_SUPER; + _sapp.keycodes[0x150] = SAPP_KEYCODE_DOWN; + _sapp.keycodes[0x14B] = SAPP_KEYCODE_LEFT; + _sapp.keycodes[0x14D] = SAPP_KEYCODE_RIGHT; + _sapp.keycodes[0x148] = SAPP_KEYCODE_UP; + _sapp.keycodes[0x052] = SAPP_KEYCODE_KP_0; + _sapp.keycodes[0x04F] = SAPP_KEYCODE_KP_1; + _sapp.keycodes[0x050] = SAPP_KEYCODE_KP_2; + _sapp.keycodes[0x051] = SAPP_KEYCODE_KP_3; + _sapp.keycodes[0x04B] = SAPP_KEYCODE_KP_4; + _sapp.keycodes[0x04C] = SAPP_KEYCODE_KP_5; + _sapp.keycodes[0x04D] = SAPP_KEYCODE_KP_6; + _sapp.keycodes[0x047] = SAPP_KEYCODE_KP_7; + _sapp.keycodes[0x048] = SAPP_KEYCODE_KP_8; + _sapp.keycodes[0x049] = SAPP_KEYCODE_KP_9; + _sapp.keycodes[0x04E] = SAPP_KEYCODE_KP_ADD; + _sapp.keycodes[0x053] = SAPP_KEYCODE_KP_DECIMAL; + _sapp.keycodes[0x135] = SAPP_KEYCODE_KP_DIVIDE; + _sapp.keycodes[0x11C] = SAPP_KEYCODE_KP_ENTER; + _sapp.keycodes[0x037] = SAPP_KEYCODE_KP_MULTIPLY; + _sapp.keycodes[0x04A] = SAPP_KEYCODE_KP_SUBTRACT; +} +#endif // _SAPP_WIN32 + +#if defined(_SAPP_WIN32) + +#if defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sapp_d3d11_Release(self) (self)->Release() +#define _sapp_win32_refiid(iid) iid +#else +#define _sapp_d3d11_Release(self) (self)->lpVtbl->Release(self) +#define _sapp_win32_refiid(iid) &iid +#endif + +#define _SAPP_SAFE_RELEASE(obj) if (obj) { _sapp_d3d11_Release(obj); obj=0; } + + +static const IID _sapp_IID_ID3D11Texture2D = { 0x6f15aaf2,0xd208,0x4e89, {0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c} }; +static const IID _sapp_IID_IDXGIDevice1 = { 0x77db970f,0x6276,0x48ba, {0xba,0x28,0x07,0x01,0x43,0xb4,0x39,0x2c} }; +static const IID _sapp_IID_IDXGIFactory = { 0x7b7166ec,0x21c7,0x44ae, {0xb2,0x1a,0xc9,0xae,0x32,0x1a,0xe3,0x69} }; + +static inline HRESULT _sapp_dxgi_GetBuffer(IDXGISwapChain* self, UINT Buffer, REFIID riid, void** ppSurface) { + #if defined(__cplusplus) + return self->GetBuffer(Buffer, riid, ppSurface); + #else + return self->lpVtbl->GetBuffer(self, Buffer, riid, ppSurface); + #endif +} + +static inline HRESULT _sapp_d3d11_QueryInterface(ID3D11Device* self, REFIID riid, void** ppvObject) { + #if defined(__cplusplus) + return self->QueryInterface(riid, ppvObject); + #else + return self->lpVtbl->QueryInterface(self, riid, ppvObject); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { + #if defined(__cplusplus) + return self->CreateRenderTargetView(pResource, pDesc, ppRTView); + #else + return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { + #if defined(__cplusplus) + return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); + #else + return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); + #endif +} + +static inline HRESULT _sapp_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { + #if defined(__cplusplus) + return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); + #else + return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); + #endif +} + +static inline HRESULT _sapp_dxgi_ResizeBuffers(IDXGISwapChain* self, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) { + #if defined(__cplusplus) + return self->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); + #else + return self->lpVtbl->ResizeBuffers(self, BufferCount, Width, Height, NewFormat, SwapChainFlags); + #endif +} + +static inline HRESULT _sapp_dxgi_Present(IDXGISwapChain* self, UINT SyncInterval, UINT Flags) { + #if defined(__cplusplus) + return self->Present(SyncInterval, Flags); + #else + return self->lpVtbl->Present(self, SyncInterval, Flags); + #endif +} + +static inline HRESULT _sapp_dxgi_GetFrameStatistics(IDXGISwapChain* self, DXGI_FRAME_STATISTICS* pStats) { + #if defined(__cplusplus) + return self->GetFrameStatistics(pStats); + #else + return self->lpVtbl->GetFrameStatistics(self, pStats); + #endif +} + +static inline HRESULT _sapp_dxgi_SetMaximumFrameLatency(IDXGIDevice1* self, UINT MaxLatency) { + #if defined(__cplusplus) + return self->SetMaximumFrameLatency(MaxLatency); + #else + return self->lpVtbl->SetMaximumFrameLatency(self, MaxLatency); + #endif +} + +static inline HRESULT _sapp_dxgi_GetAdapter(IDXGIDevice1* self, IDXGIAdapter** pAdapter) { + #if defined(__cplusplus) + return self->GetAdapter(pAdapter); + #else + return self->lpVtbl->GetAdapter(self, pAdapter); + #endif +} + +static inline HRESULT _sapp_dxgi_GetParent(IDXGIObject* self, REFIID riid, void** ppParent) { + #if defined(__cplusplus) + return self->GetParent(riid, ppParent); + #else + return self->lpVtbl->GetParent(self, riid, ppParent); + #endif +} + +static inline HRESULT _sapp_dxgi_MakeWindowAssociation(IDXGIFactory* self, HWND WindowHandle, UINT Flags) { + #if defined(__cplusplus) + return self->MakeWindowAssociation(WindowHandle, Flags); + #else + return self->lpVtbl->MakeWindowAssociation(self, WindowHandle, Flags); + #endif +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_device_and_swapchain(void) { + DXGI_SWAP_CHAIN_DESC* sc_desc = &_sapp.d3d11.swap_chain_desc; + sc_desc->BufferDesc.Width = (UINT)_sapp.framebuffer_width; + sc_desc->BufferDesc.Height = (UINT)_sapp.framebuffer_height; + sc_desc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + sc_desc->BufferDesc.RefreshRate.Numerator = 60; + sc_desc->BufferDesc.RefreshRate.Denominator = 1; + sc_desc->OutputWindow = _sapp.win32.hwnd; + sc_desc->Windowed = true; + if (_sapp.win32.is_win10_or_greater) { + sc_desc->BufferCount = 2; + sc_desc->SwapEffect = (DXGI_SWAP_EFFECT) _SAPP_DXGI_SWAP_EFFECT_FLIP_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = true; + } + else { + sc_desc->BufferCount = 1; + sc_desc->SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + _sapp.d3d11.use_dxgi_frame_stats = false; + } + sc_desc->SampleDesc.Count = 1; + sc_desc->SampleDesc.Quality = 0; + sc_desc->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + UINT create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_BGRA_SUPPORT; + #if defined(SOKOL_DEBUG) + create_flags |= D3D11_CREATE_DEVICE_DEBUG; + #endif + D3D_FEATURE_LEVEL feature_level; + HRESULT hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + _SOKOL_UNUSED(hr); + #if defined(SOKOL_DEBUG) + if (!SUCCEEDED(hr)) { + // if initialization with D3D11_CREATE_DEVICE_DEBUG fails, this could be because the + // 'D3D11 debug layer' stopped working, indicated by the error message: + // === + // D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system. + // These flags must be removed, or the Windows 10 SDK must be installed. + // Flags include: D3D11_CREATE_DEVICE_DEBUG + // === + // + // ...just retry with the DEBUG flag switched off + _SAPP_ERROR(WIN32_D3D11_CREATE_DEVICE_AND_SWAPCHAIN_WITH_DEBUG_FAILED); + create_flags &= ~(UINT)D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11CreateDeviceAndSwapChain( + NULL, /* pAdapter (use default) */ + D3D_DRIVER_TYPE_HARDWARE, /* DriverType */ + NULL, /* Software */ + create_flags, /* Flags */ + NULL, /* pFeatureLevels */ + 0, /* FeatureLevels */ + D3D11_SDK_VERSION, /* SDKVersion */ + sc_desc, /* pSwapChainDesc */ + &_sapp.d3d11.swap_chain, /* ppSwapChain */ + &_sapp.d3d11.device, /* ppDevice */ + &feature_level, /* pFeatureLevel */ + &_sapp.d3d11.device_context); /* ppImmediateContext */ + } + #endif + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context); + + // minimize frame latency, disable Alt-Enter + hr = _sapp_d3d11_QueryInterface(_sapp.d3d11.device, _sapp_win32_refiid(_sapp_IID_IDXGIDevice1), (void**)&_sapp.d3d11.dxgi_device); + if (SUCCEEDED(hr) && _sapp.d3d11.dxgi_device) { + _sapp_dxgi_SetMaximumFrameLatency(_sapp.d3d11.dxgi_device, 1); + IDXGIAdapter* dxgi_adapter = 0; + hr = _sapp_dxgi_GetAdapter(_sapp.d3d11.dxgi_device, &dxgi_adapter); + if (SUCCEEDED(hr) && dxgi_adapter) { + IDXGIFactory* dxgi_factory = 0; + hr = _sapp_dxgi_GetParent((IDXGIObject*)dxgi_adapter, _sapp_win32_refiid(_sapp_IID_IDXGIFactory), (void**)&dxgi_factory); + if (SUCCEEDED(hr)) { + _sapp_dxgi_MakeWindowAssociation(dxgi_factory, _sapp.win32.hwnd, DXGI_MWA_NO_ALT_ENTER|DXGI_MWA_NO_PRINT_SCREEN); + _SAPP_SAFE_RELEASE(dxgi_factory); + } + else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIFACTORY_FAILED); + } + _SAPP_SAFE_RELEASE(dxgi_adapter); + } + else { + _SAPP_ERROR(WIN32_D3D11_GET_IDXGIADAPTER_FAILED); + } + } + else { + _SAPP_PANIC(WIN32_D3D11_QUERY_INTERFACE_IDXGIDEVICE1_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_device_and_swapchain(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.swap_chain); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dxgi_device); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device_context); + _SAPP_SAFE_RELEASE(_sapp.d3d11.device); +} + +_SOKOL_PRIVATE void _sapp_d3d11_create_default_render_target(void) { + SOKOL_ASSERT(0 == _sapp.d3d11.rt); + SOKOL_ASSERT(0 == _sapp.d3d11.rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rt); + SOKOL_ASSERT(0 == _sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(0 == _sapp.d3d11.ds); + SOKOL_ASSERT(0 == _sapp.d3d11.dsv); + + HRESULT hr; _SOKOL_UNUSED(hr); + + /* view for the swapchain-created framebuffer */ + hr = _sapp_dxgi_GetBuffer(_sapp.d3d11.swap_chain, 0, _sapp_win32_refiid(_sapp_IID_ID3D11Texture2D), (void**)&_sapp.d3d11.rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.rt, NULL, &_sapp.d3d11.rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.rtv); + + /* common desc for MSAA and depth-stencil texture */ + D3D11_TEXTURE2D_DESC tex_desc; + _sapp_clear(&tex_desc, sizeof(tex_desc)); + tex_desc.Width = (UINT)_sapp.framebuffer_width; + tex_desc.Height = (UINT)_sapp.framebuffer_height; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Usage = D3D11_USAGE_DEFAULT; + tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; + tex_desc.SampleDesc.Count = (UINT) _sapp.sample_count; + tex_desc.SampleDesc.Quality = (UINT) (_sapp.sample_count > 1 ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + + /* create MSAA texture and view if antialiasing requested */ + if (_sapp.sample_count > 1) { + tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.msaa_rt); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rt); + hr = _sapp_d3d11_CreateRenderTargetView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.msaa_rt, NULL, &_sapp.d3d11.msaa_rtv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.msaa_rtv); + } + + /* texture and view for the depth-stencil-surface */ + tex_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + tex_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + hr = _sapp_d3d11_CreateTexture2D(_sapp.d3d11.device, &tex_desc, NULL, &_sapp.d3d11.ds); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.ds); + hr = _sapp_d3d11_CreateDepthStencilView(_sapp.d3d11.device, (ID3D11Resource*)_sapp.d3d11.ds, NULL, &_sapp.d3d11.dsv); + SOKOL_ASSERT(SUCCEEDED(hr) && _sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_destroy_default_render_target(void) { + _SAPP_SAFE_RELEASE(_sapp.d3d11.rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rt); + _SAPP_SAFE_RELEASE(_sapp.d3d11.msaa_rtv); + _SAPP_SAFE_RELEASE(_sapp.d3d11.ds); + _SAPP_SAFE_RELEASE(_sapp.d3d11.dsv); +} + +_SOKOL_PRIVATE void _sapp_d3d11_resize_default_render_target(void) { + if (_sapp.d3d11.swap_chain) { + _sapp_d3d11_destroy_default_render_target(); + _sapp_dxgi_ResizeBuffers(_sapp.d3d11.swap_chain, _sapp.d3d11.swap_chain_desc.BufferCount, (UINT)_sapp.framebuffer_width, (UINT)_sapp.framebuffer_height, DXGI_FORMAT_B8G8R8A8_UNORM, 0); + _sapp_d3d11_create_default_render_target(); + } +} + +_SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) { + UINT flags = 0; + if (_sapp.win32.is_win10_or_greater && do_not_wait) { + /* this hack/workaround somewhat improves window-movement and -sizing + responsiveness when rendering is controlled via WM_TIMER during window + move and resize on NVIDIA cards on Win10 with recent drivers. + */ + flags = DXGI_PRESENT_DO_NOT_WAIT; + } + _sapp_dxgi_Present(_sapp.d3d11.swap_chain, (UINT)_sapp.swap_interval, flags); +} + +#endif /* SOKOL_D3D11 */ + +#if defined(SOKOL_GLCORE) +_SOKOL_PRIVATE void _sapp_wgl_init(void) { + _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); + if (!_sapp.wgl.opengl32) { + _SAPP_PANIC(WIN32_LOAD_OPENGL32_DLL_FAILED); + } + SOKOL_ASSERT(_sapp.wgl.opengl32); + _sapp.wgl.CreateContext = (PFN_wglCreateContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglCreateContext"); + SOKOL_ASSERT(_sapp.wgl.CreateContext); + _sapp.wgl.DeleteContext = (PFN_wglDeleteContext)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglDeleteContext"); + SOKOL_ASSERT(_sapp.wgl.DeleteContext); + _sapp.wgl.GetProcAddress = (PFN_wglGetProcAddress)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetProcAddress"); + SOKOL_ASSERT(_sapp.wgl.GetProcAddress); + _sapp.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglGetCurrentDC"); + SOKOL_ASSERT(_sapp.wgl.GetCurrentDC); + _sapp.wgl.MakeCurrent = (PFN_wglMakeCurrent)(void*) GetProcAddress(_sapp.wgl.opengl32, "wglMakeCurrent"); + SOKOL_ASSERT(_sapp.wgl.MakeCurrent); + _sapp.wgl.GetIntegerv = (void(WINAPI*)(uint32_t, int32_t*)) GetProcAddress(_sapp.wgl.opengl32, "glGetIntegerv"); + SOKOL_ASSERT(_sapp.wgl.GetIntegerv); + + _sapp.wgl.msg_hwnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, + L"SOKOLAPP", + L"sokol-app message window", + WS_CLIPSIBLINGS|WS_CLIPCHILDREN, + 0, 0, 1, 1, + NULL, NULL, + GetModuleHandleW(NULL), + NULL); + if (!_sapp.wgl.msg_hwnd) { + _SAPP_PANIC(WIN32_CREATE_HELPER_WINDOW_FAILED); + } + SOKOL_ASSERT(_sapp.wgl.msg_hwnd); + ShowWindow(_sapp.wgl.msg_hwnd, SW_HIDE); + MSG msg; + while (PeekMessageW(&msg, _sapp.wgl.msg_hwnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + _sapp.wgl.msg_dc = GetDC(_sapp.wgl.msg_hwnd); + if (!_sapp.wgl.msg_dc) { + _SAPP_PANIC(WIN32_HELPER_WINDOW_GETDC_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_wgl_shutdown(void) { + SOKOL_ASSERT(_sapp.wgl.opengl32 && _sapp.wgl.msg_hwnd); + DestroyWindow(_sapp.wgl.msg_hwnd); _sapp.wgl.msg_hwnd = 0; + FreeLibrary(_sapp.wgl.opengl32); _sapp.wgl.opengl32 = 0; +} + +_SOKOL_PRIVATE bool _sapp_wgl_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext && extensions); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_wgl_ext_supported(const char* ext) { + SOKOL_ASSERT(ext); + if (_sapp.wgl.GetExtensionsStringEXT) { + const char* extensions = _sapp.wgl.GetExtensionsStringEXT(); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + if (_sapp.wgl.GetExtensionsStringARB) { + const char* extensions = _sapp.wgl.GetExtensionsStringARB(_sapp.wgl.GetCurrentDC()); + if (extensions) { + if (_sapp_wgl_has_ext(ext, extensions)) { + return true; + } + } + } + return false; +} + +_SOKOL_PRIVATE void _sapp_wgl_load_extensions(void) { + SOKOL_ASSERT(_sapp.wgl.msg_dc); + PIXELFORMATDESCRIPTOR pfd; + _sapp_clear(&pfd, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + if (!SetPixelFormat(_sapp.wgl.msg_dc, ChoosePixelFormat(_sapp.wgl.msg_dc, &pfd), &pfd)) { + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_SET_PIXELFORMAT_FAILED); + } + HGLRC rc = _sapp.wgl.CreateContext(_sapp.wgl.msg_dc); + if (!rc) { + _SAPP_PANIC(WIN32_CREATE_DUMMY_CONTEXT_FAILED); + } + if (!_sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, rc)) { + _SAPP_PANIC(WIN32_DUMMY_CONTEXT_MAKE_CURRENT_FAILED); + } + _sapp.wgl.GetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringEXT"); + _sapp.wgl.GetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetExtensionsStringARB"); + _sapp.wgl.CreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)(void*) _sapp.wgl.GetProcAddress("wglCreateContextAttribsARB"); + _sapp.wgl.SwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(void*) _sapp.wgl.GetProcAddress("wglSwapIntervalEXT"); + _sapp.wgl.GetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(void*) _sapp.wgl.GetProcAddress("wglGetPixelFormatAttribivARB"); + _sapp.wgl.arb_multisample = _sapp_wgl_ext_supported("WGL_ARB_multisample"); + _sapp.wgl.arb_create_context = _sapp_wgl_ext_supported("WGL_ARB_create_context"); + _sapp.wgl.arb_create_context_profile = _sapp_wgl_ext_supported("WGL_ARB_create_context_profile"); + _sapp.wgl.ext_swap_control = _sapp_wgl_ext_supported("WGL_EXT_swap_control"); + _sapp.wgl.arb_pixel_format = _sapp_wgl_ext_supported("WGL_ARB_pixel_format"); + _sapp.wgl.MakeCurrent(_sapp.wgl.msg_dc, 0); + _sapp.wgl.DeleteContext(rc); +} + +_SOKOL_PRIVATE int _sapp_wgl_attrib(int pixel_format, int attrib) { + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + int value = 0; + if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, 1, &attrib, &value)) { + _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); + } + return value; +} + +_SOKOL_PRIVATE void _sapp_wgl_attribiv(int pixel_format, int num_attribs, const int* attribs, int* results) { + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + if (!_sapp.wgl.GetPixelFormatAttribivARB(_sapp.win32.dc, pixel_format, 0, num_attribs, attribs, results)) { + _SAPP_PANIC(WIN32_GET_PIXELFORMAT_ATTRIB_FAILED); + } +} + +_SOKOL_PRIVATE int _sapp_wgl_find_pixel_format(void) { + SOKOL_ASSERT(_sapp.win32.dc); + SOKOL_ASSERT(_sapp.wgl.arb_pixel_format); + + #define _sapp_wgl_num_query_tags (12) + const int query_tags[_sapp_wgl_num_query_tags] = { + WGL_SUPPORT_OPENGL_ARB, + WGL_DRAW_TO_WINDOW_ARB, + WGL_PIXEL_TYPE_ARB, + WGL_ACCELERATION_ARB, + WGL_DOUBLE_BUFFER_ARB, + WGL_RED_BITS_ARB, + WGL_GREEN_BITS_ARB, + WGL_BLUE_BITS_ARB, + WGL_ALPHA_BITS_ARB, + WGL_DEPTH_BITS_ARB, + WGL_STENCIL_BITS_ARB, + WGL_SAMPLES_ARB, + }; + const int result_support_opengl_index = 0; + const int result_draw_to_window_index = 1; + const int result_pixel_type_index = 2; + const int result_acceleration_index = 3; + const int result_double_buffer_index = 4; + const int result_red_bits_index = 5; + const int result_green_bits_index = 6; + const int result_blue_bits_index = 7; + const int result_alpha_bits_index = 8; + const int result_depth_bits_index = 9; + const int result_stencil_bits_index = 10; + const int result_samples_index = 11; + + int query_results[_sapp_wgl_num_query_tags] = {0}; + // Drop the last item if multisample extension is not supported. + // If in future querying with multiple extensions, will have to shuffle index values to have active extensions on the end. + int query_count = _sapp_wgl_num_query_tags; + if (!_sapp.wgl.arb_multisample) { + query_count = _sapp_wgl_num_query_tags - 1; + } + + int native_count = _sapp_wgl_attrib(1, WGL_NUMBER_PIXEL_FORMATS_ARB); + + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = (_sapp.sample_count > 1) ? _sapp.sample_count : 0; + + int pixel_format = 0; + + _sapp_gl_fbselect fbselect; + _sapp_gl_init_fbselect(&fbselect); + for (int i = 0; i < native_count; i++) { + const int n = i + 1; + _sapp_wgl_attribiv(n, query_count, query_tags, query_results); + + if (query_results[result_support_opengl_index] == 0 + || query_results[result_draw_to_window_index] == 0 + || query_results[result_pixel_type_index] != WGL_TYPE_RGBA_ARB + || query_results[result_acceleration_index] == WGL_NO_ACCELERATION_ARB) + { + continue; + } + + _sapp_gl_fbconfig u; + _sapp_clear(&u, sizeof(u)); + u.red_bits = query_results[result_red_bits_index]; + u.green_bits = query_results[result_green_bits_index]; + u.blue_bits = query_results[result_blue_bits_index]; + u.alpha_bits = query_results[result_alpha_bits_index]; + u.depth_bits = query_results[result_depth_bits_index]; + u.stencil_bits = query_results[result_stencil_bits_index]; + u.doublebuffer = 0 != query_results[result_double_buffer_index]; + u.samples = query_results[result_samples_index]; // NOTE: If arb_multisample is not supported - just takes the default 0 + + // Test if this pixel format is better than the previous one + if (_sapp_gl_select_fbconfig(&fbselect, &desired, &u)) { + pixel_format = (uintptr_t)n; + + // Early exit if matching as good as possible + if (fbselect.best_match) { + break; + } + } + } + + return pixel_format; +} + +_SOKOL_PRIVATE void _sapp_wgl_create_context(void) { + int pixel_format = _sapp_wgl_find_pixel_format(); + if (0 == pixel_format) { + _SAPP_PANIC(WIN32_WGL_FIND_PIXELFORMAT_FAILED); + } + PIXELFORMATDESCRIPTOR pfd; + if (!DescribePixelFormat(_sapp.win32.dc, pixel_format, sizeof(pfd), &pfd)) { + _SAPP_PANIC(WIN32_WGL_DESCRIBE_PIXELFORMAT_FAILED); + } + if (!SetPixelFormat(_sapp.win32.dc, pixel_format, &pfd)) { + _SAPP_PANIC(WIN32_WGL_SET_PIXELFORMAT_FAILED); + } + if (!_sapp.wgl.arb_create_context) { + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_REQUIRED); + } + if (!_sapp.wgl.arb_create_context_profile) { + _SAPP_PANIC(WIN32_WGL_ARB_CREATE_CONTEXT_PROFILE_REQUIRED); + } + const int attrs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + WGL_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, +#if defined(SOKOL_DEBUG) + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB, +#else + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, +#endif + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, 0 + }; + _sapp.wgl.gl_ctx = _sapp.wgl.CreateContextAttribsARB(_sapp.win32.dc, 0, attrs); + if (!_sapp.wgl.gl_ctx) { + const DWORD err = GetLastError(); + if (err == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) { + _SAPP_PANIC(WIN32_WGL_OPENGL_VERSION_NOT_SUPPORTED); + } + else if (err == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) { + _SAPP_PANIC(WIN32_WGL_OPENGL_PROFILE_NOT_SUPPORTED); + } + else if (err == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) { + _SAPP_PANIC(WIN32_WGL_INCOMPATIBLE_DEVICE_CONTEXT); + } + else { + _SAPP_PANIC(WIN32_WGL_CREATE_CONTEXT_ATTRIBS_FAILED_OTHER); + } + } + _sapp.wgl.MakeCurrent(_sapp.win32.dc, _sapp.wgl.gl_ctx); + if (_sapp.wgl.ext_swap_control) { + /* FIXME: DwmIsCompositionEnabled() (see GLFW) */ + _sapp.wgl.SwapIntervalEXT(_sapp.swap_interval); + } + const uint32_t gl_framebuffer_binding = 0x8CA6; + _sapp.wgl.GetIntegerv(gl_framebuffer_binding, (int32_t*)&_sapp.gl.framebuffer); +} + +_SOKOL_PRIVATE void _sapp_wgl_destroy_context(void) { + SOKOL_ASSERT(_sapp.wgl.gl_ctx); + _sapp.wgl.DeleteContext(_sapp.wgl.gl_ctx); + _sapp.wgl.gl_ctx = 0; +} + +_SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) { + SOKOL_ASSERT(_sapp.win32.dc); + /* FIXME: DwmIsCompositionEnabled? (see GLFW) */ + SwapBuffers(_sapp.win32.dc); +} +#endif /* SOKOL_GLCORE */ + +_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sapp_clear(dst, (size_t)dst_num_bytes); + const int bytes_needed = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); + if (bytes_needed <= dst_num_bytes) { + WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_num_bytes, NULL, NULL); + return true; + } + else { + return false; + } +} + +/* updates current window and framebuffer size from the window's client rect, returns true if size has changed */ +_SOKOL_PRIVATE bool _sapp_win32_update_dimensions(void) { + RECT rect; + if (GetClientRect(_sapp.win32.hwnd, &rect)) { + float window_width = (float)(rect.right - rect.left) / _sapp.win32.dpi.window_scale; + float window_height = (float)(rect.bottom - rect.top) / _sapp.win32.dpi.window_scale; + _sapp.window_width = (int)roundf(window_width); + _sapp.window_height = (int)roundf(window_height); + int fb_width = (int)roundf(window_width * _sapp.win32.dpi.content_scale); + int fb_height = (int)roundf(window_height * _sapp.win32.dpi.content_scale); + /* prevent a framebuffer size of 0 when window is minimized */ + if (0 == fb_width) { + fb_width = 1; + } + if (0 == fb_height) { + fb_height = 1; + } + if ((fb_width != _sapp.framebuffer_width) || (fb_height != _sapp.framebuffer_height)) { + _sapp.framebuffer_width = fb_width; + _sapp.framebuffer_height = fb_height; + return true; + } + } + else { + _sapp.window_width = _sapp.window_height = 1; + _sapp.framebuffer_width = _sapp.framebuffer_height = 1; + } + return false; +} + +_SOKOL_PRIVATE void _sapp_win32_set_fullscreen(bool fullscreen, UINT swp_flags) { + HMONITOR monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO minfo; + _sapp_clear(&minfo, sizeof(minfo)); + minfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(monitor, &minfo); + const RECT mr = minfo.rcMonitor; + const int monitor_w = mr.right - mr.left; + const int monitor_h = mr.bottom - mr.top; + + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD win_style; + RECT rect = { 0, 0, 0, 0 }; + + _sapp.fullscreen = fullscreen; + if (!_sapp.fullscreen) { + win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect = _sapp.win32.stored_window_rect; + } + else { + GetWindowRect(_sapp.win32.hwnd, &_sapp.win32.stored_window_rect); + win_style = WS_POPUP | WS_SYSMENU | WS_VISIBLE; + rect.left = mr.left; + rect.top = mr.top; + rect.right = rect.left + monitor_w; + rect.bottom = rect.top + monitor_h; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + } + const int win_w = rect.right - rect.left; + const int win_h = rect.bottom - rect.top; + const int win_x = rect.left; + const int win_y = rect.top; + SetWindowLongPtr(_sapp.win32.hwnd, GWL_STYLE, win_style); + SetWindowPos(_sapp.win32.hwnd, HWND_TOP, win_x, win_y, win_w, win_h, swp_flags | SWP_FRAMECHANGED); +} + +_SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { + _sapp_win32_set_fullscreen(!_sapp.fullscreen, SWP_SHOWWINDOW); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined + // before windows.h is included, but we can't guarantee that because + // the sokol_app.h implementation may be included with other implementations + // in the same compilation unit + int id = 0; + switch (cursor) { + case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL + case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM + case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS + case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND + case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE + case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS + case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE + case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW + case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL + case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO + default: break; + } + if (id != 0) { + _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); + } + // fallback: default cursor + if (0 == _sapp.win32.cursors[cursor]) { + // 32512 => IDC_ARROW + _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); + } + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + _sapp_win32_init_cursor((sapp_mouse_cursor)i); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { + POINT pos; + if (!GetCursorPos(&pos)) { + return false; + } + if (WindowFromPoint(pos) != _sapp.win32.hwnd) { + return false; + } + RECT area; + GetClientRect(_sapp.win32.hwnd, &area); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); + return PtInRect(&area, pos) == TRUE; +} + +_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { + // NOTE: when called from WM_SETCURSOR, the area test would be redundant + if (!skip_area_test) { + if (!_sapp_win32_cursor_in_content_area()) { + return; + } + } + if (!shown) { + SetCursor(NULL); + } + else { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); + SetCursor(_sapp.win32.cursors[cursor]); + } +} + +_SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { + if (0 == _sapp.win32.mouse.capture_mask) { + SetCapture(_sapp.win32.hwnd); + } + _sapp.win32.mouse.capture_mask |= btn_mask; +} + +_SOKOL_PRIVATE void _sapp_win32_release_mouse(uint8_t btn_mask) { + if (0 != _sapp.win32.mouse.capture_mask) { + _sapp.win32.mouse.capture_mask &= ~btn_mask; + if (0 == _sapp.win32.mouse.capture_mask) { + ReleaseCapture(); + } + } +} + +_SOKOL_PRIVATE bool _sapp_win32_is_foreground_window(void) { + return _sapp.win32.hwnd == GetForegroundWindow(); +} + +_SOKOL_PRIVATE void _sapp_win32_lock_mouse(bool lock) { + _sapp.win32.mouse.requested_lock = lock; +} + +_SOKOL_PRIVATE void _sapp_win32_do_lock_mouse(void) { + _sapp.mouse.locked = true; + + // hide mouse cursor (NOTE: this maintains a hidden counter, but since + // only mouse-lock uses ShowCursor this doesn't matter) + ShowCursor(FALSE); + + // reset dx/dy and release any active mouse capture + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_win32_release_mouse(0xFF); + + // store current mouse position so that it can be restored when unlocked + POINT pos; + if (GetCursorPos(&pos)) { + _sapp.win32.mouse.lock.pos_valid = true; + _sapp.win32.mouse.lock.pos_x = pos.x; + _sapp.win32.mouse.lock.pos_y = pos.y; + } else { + _sapp.win32.mouse.lock.pos_valid = false; + } + + // while mouse is locked, restrict cursor movement to the client + // rectangle so that we don't loose any mouse movement events + RECT client_rect; + GetClientRect(_sapp.win32.hwnd, &client_rect); + POINT mid_point; + mid_point.x = (client_rect.right - client_rect.left) / 2; + mid_point.y = (client_rect.bottom - client_rect.top) / 2; + ClientToScreen(_sapp.win32.hwnd, &mid_point); + RECT clip_rect; + clip_rect.left = clip_rect.right = mid_point.x; + clip_rect.top = clip_rect.bottom = mid_point.y; + ClipCursor(&clip_rect); + + // enable raw input for mouse, starts sending WM_INPUT messages to WinProc (see GLFW) + const RAWINPUTDEVICE rid = { + 0x01, // usUsagePage: HID_USAGE_PAGE_GENERIC + 0x02, // usUsage: HID_USAGE_GENERIC_MOUSE + 0, // dwFlags + _sapp.win32.hwnd // hwndTarget + }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_LOCK); + } + // in case the raw mouse device only supports absolute position reporting, + // we need to skip the dx/dy compution for the first WM_INPUT event + _sapp.win32.mouse.raw_input.pos_valid = false; +} + +_SOKOL_PRIVATE void _sapp_win32_do_unlock_mouse(void) { + _sapp.mouse.locked = false; + + // make mouse cursor visible + ShowCursor(TRUE); + + // reset dx/dy and release any active mouse capture + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp_win32_release_mouse(0xFF); + + // disable raw input for mouse + const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + _SAPP_ERROR(WIN32_REGISTER_RAW_INPUT_DEVICES_FAILED_MOUSE_UNLOCK); + } + + // unrestrict mouse movement + ClipCursor(NULL); + + // restore the 'pre-locked' mouse position + if (_sapp.win32.mouse.lock.pos_valid) { + SetCursorPos(_sapp.win32.mouse.lock.pos_x, _sapp.win32.mouse.lock.pos_y); + _sapp.win32.mouse.lock.pos_valid = false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_update_mouse_lock(void) { + // mouse lock can only be active when we're the active window + if (!_sapp_win32_is_foreground_window()) { + // unlock mouse if currently locked + if (_sapp.mouse.locked) { + _sapp_win32_do_unlock_mouse(); + } + return; + } + + // nothing to do if requested lock state matches current lock state + const bool lock = _sapp.win32.mouse.requested_lock; + if (lock == _sapp.mouse.locked) { + return; + } + + // otherwise change into desired state + if (lock) { + _sapp_win32_do_lock_mouse(); + } else { + _sapp_win32_do_unlock_mouse(); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_update_monitor(void) { + const HMONITOR cur_monitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + if (cur_monitor != _sapp.win32.hmonitor) { + _sapp.win32.hmonitor = cur_monitor; + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_win32_mods(void) { + uint32_t mods = 0; + if (GetKeyState(VK_SHIFT) & (1<<15)) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (GetKeyState(VK_CONTROL) & (1<<15)) { + mods |= SAPP_MODIFIER_CTRL; + } + if (GetKeyState(VK_MENU) & (1<<15)) { + mods |= SAPP_MODIFIER_ALT; + } + if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1<<15)) { + mods |= SAPP_MODIFIER_SUPER; + } + const bool swapped = (TRUE == GetSystemMetrics(SM_SWAPBUTTON)); + if (GetAsyncKeyState(VK_LBUTTON)) { + mods |= swapped ? SAPP_MODIFIER_RMB : SAPP_MODIFIER_LMB; + } + if (GetAsyncKeyState(VK_RBUTTON)) { + mods |= swapped ? SAPP_MODIFIER_LMB : SAPP_MODIFIER_RMB; + } + if (GetAsyncKeyState(VK_MBUTTON)) { + mods |= SAPP_MODIFIER_MMB; + } + return mods; +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_update(LPARAM lParam) { + if (!_sapp.mouse.locked) { + const float new_x = (float)GET_X_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + const float new_y = (float)GET_Y_LPARAM(lParam) * _sapp.win32.dpi.mouse_scale; + if (_sapp.mouse.pos_valid) { + // don't update dx/dy in the very first event + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_win32_mouse_event(sapp_event_type type, sapp_mousebutton btn) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.mouse_button = btn; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_scroll_event(float x, float y) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.scroll_x = -x / 30.0f; + _sapp.event.scroll_y = y / 30.0f; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_key_event(sapp_event_type type, int vk, bool repeat) { + if (_sapp_events_enabled() && (vk < SAPP_MAX_KEYCODES)) { + _sapp_init_event(type); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.key_code = _sapp.keycodes[vk]; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_win32_char_event(uint32_t c, bool repeat) { + if (_sapp_events_enabled() && (c >= 32)) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp.event.char_code = c; + _sapp.event.key_repeat = repeat; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_win32_dpi_changed(HWND hWnd, LPRECT proposed_win_rect) { + /* called on WM_DPICHANGED, which will only be sent to the application + if sapp_desc.high_dpi is true and the Windows version is recent enough + to support DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + */ + SOKOL_ASSERT(_sapp.desc.high_dpi); + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (!user32) { + return; + } + typedef UINT(WINAPI * GETDPIFORWINDOW_T)(HWND hwnd); + GETDPIFORWINDOW_T fn_getdpiforwindow = (GETDPIFORWINDOW_T)(void*)GetProcAddress(user32, "GetDpiForWindow"); + if (fn_getdpiforwindow) { + UINT dpix = fn_getdpiforwindow(_sapp.win32.hwnd); + // NOTE: for high-dpi apps, mouse_scale remains one + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.dpi_scale = _sapp.win32.dpi.window_scale; + SetWindowPos(hWnd, 0, + proposed_win_rect->left, + proposed_win_rect->top, + proposed_win_rect->right - proposed_win_rect->left, + proposed_win_rect->bottom - proposed_win_rect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + } + FreeLibrary(user32); +} + +_SOKOL_PRIVATE void _sapp_win32_files_dropped(HDROP hdrop) { + if (!_sapp.drop.enabled) { + return; + } + _sapp_clear_drop_buffer(); + bool drop_failed = false; + const int count = (int) DragQueryFileW(hdrop, 0xffffffff, NULL, 0); + _sapp.drop.num_files = (count > _sapp.drop.max_files) ? _sapp.drop.max_files : count; + for (UINT i = 0; i < (UINT)_sapp.drop.num_files; i++) { + const UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; + WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); + DragQueryFileW(hdrop, i, buffer, num_chars); + if (!_sapp_win32_wide_to_utf8(buffer, _sapp_dropped_file_path_ptr((int)i), _sapp.drop.max_path_length)) { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + drop_failed = true; + } + _sapp_free(buffer); + } + DragFinish(hdrop); + if (!drop_failed) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp.event.modifiers = _sapp_win32_mods(); + _sapp_call_event(&_sapp.event); + } + } + else { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + } +} + +_SOKOL_PRIVATE void _sapp_win32_timing_measure(void) { + #if defined(SOKOL_D3D11) + // on D3D11, use the more precise DXGI timestamp + if (_sapp.d3d11.use_dxgi_frame_stats) { + DXGI_FRAME_STATISTICS dxgi_stats; + _sapp_clear(&dxgi_stats, sizeof(dxgi_stats)); + HRESULT hr = _sapp_dxgi_GetFrameStatistics(_sapp.d3d11.swap_chain, &dxgi_stats); + if (SUCCEEDED(hr)) { + if (dxgi_stats.SyncRefreshCount != _sapp.d3d11.sync_refresh_count) { + if ((_sapp.d3d11.sync_refresh_count + 1) != dxgi_stats.SyncRefreshCount) { + _sapp_timing_discontinuity(&_sapp.timing); + } + _sapp.d3d11.sync_refresh_count = dxgi_stats.SyncRefreshCount; + LARGE_INTEGER qpc = dxgi_stats.SyncQPCTime; + const uint64_t now = (uint64_t)_sapp_int64_muldiv(qpc.QuadPart - _sapp.timing.timestamp.win.start.QuadPart, 1000000000, _sapp.timing.timestamp.win.freq.QuadPart); + _sapp_timing_external(&_sapp.timing, (double)now / 1000000000.0); + } + return; + } + } + // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason + _sapp_timing_measure(&_sapp.timing); + #endif + #if defined(SOKOL_GLCORE) + _sapp_timing_measure(&_sapp.timing); + #endif + #if defined(SOKOL_NOAPI) + _sapp_timing_measure(&_sapp.timing); + #endif +} + +_SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (!_sapp.win32.in_create_window) { + switch (uMsg) { + case WM_CLOSE: + /* only give user a chance to intervene when sapp_quit() wasn't already called */ + if (!_sapp.quit_ordered) { + /* if window should be closed and event handling is enabled, give user code + a change to intervene via sapp_cancel_quit() + */ + _sapp.quit_requested = true; + _sapp_win32_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + if (_sapp.quit_ordered) { + PostQuitMessage(0); + } + return 0; + case WM_SYSCOMMAND: + switch (wParam & 0xFFF0) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + if (_sapp.fullscreen) { + /* disable screen saver and blanking in fullscreen mode */ + return 0; + } + break; + case SC_KEYMENU: + /* user trying to access menu via ALT */ + return 0; + } + break; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + { + const bool iconified = wParam == SIZE_MINIMIZED; + if (iconified != _sapp.win32.iconified) { + _sapp.win32.iconified = iconified; + if (iconified) { + _sapp_win32_app_event(SAPP_EVENTTYPE_ICONIFIED); + } + else { + _sapp_win32_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + break; + case WM_SETFOCUS: + _sapp_win32_app_event(SAPP_EVENTTYPE_FOCUSED); + break; + case WM_KILLFOCUS: + _sapp_win32_app_event(SAPP_EVENTTYPE_UNFOCUSED); + break; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) { + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); + return TRUE; + } + break; + case WM_DPICHANGED: + { + /* Update window's DPI and size if its moved to another monitor with a different DPI + Only sent if DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. + */ + _sapp_win32_dpi_changed(hWnd, (LPRECT)lParam); + break; + } + case WM_LBUTTONDOWN: + _sapp_win32_mouse_update(lParam); + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, SAPP_MOUSEBUTTON_LEFT); + _sapp_win32_capture_mouse(1<data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + /* mouse only reports absolute position + NOTE: This code is untested and will most likely behave wrong in Remote Desktop sessions. + (such remote desktop sessions are setting the MOUSE_MOVE_ABSOLUTE flag). + See: https://github.com/floooh/sokol/issues/806 and + https://github.com/microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555) + */ + LONG new_x = raw_mouse_data->data.mouse.lLastX; + LONG new_y = raw_mouse_data->data.mouse.lLastY; + if (_sapp.win32.mouse.raw_input.pos_valid) { + _sapp.mouse.dx = (float) (new_x - _sapp.win32.mouse.raw_input.pos_x); + _sapp.mouse.dy = (float) (new_y - _sapp.win32.mouse.raw_input.pos_y); + } + _sapp.win32.mouse.raw_input.pos_x = new_x; + _sapp.win32.mouse.raw_input.pos_y = new_y; + _sapp.win32.mouse.raw_input.pos_valid = true; + } + else { + /* mouse reports movement delta (this seems to be the common case) */ + _sapp.mouse.dx = (float) raw_mouse_data->data.mouse.lLastX; + _sapp.mouse.dy = (float) raw_mouse_data->data.mouse.lLastY; + } + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + + case WM_MOUSELEAVE: + if (!_sapp.mouse.locked) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.win32.mouse.tracked = false; + _sapp_win32_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID); + } + break; + case WM_MOUSEWHEEL: + _sapp_win32_scroll_event(0.0f, (float)((SHORT)HIWORD(wParam))); + break; + case WM_MOUSEHWHEEL: + _sapp_win32_scroll_event((float)((SHORT)HIWORD(wParam)), 0.0f); + break; + case WM_CHAR: + _sapp_win32_char_event((uint32_t)wParam, !!(lParam&0x40000000)); + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_DOWN, (int)(HIWORD(lParam)&0x1FF), !!(lParam&0x40000000)); + break; + case WM_KEYUP: + case WM_SYSKEYUP: + _sapp_win32_key_event(SAPP_EVENTTYPE_KEY_UP, (int)(HIWORD(lParam)&0x1FF), false); + break; + case WM_ENTERSIZEMOVE: + SetTimer(_sapp.win32.hwnd, 1, USER_TIMER_MINIMUM, NULL); + break; + case WM_EXITSIZEMOVE: + KillTimer(_sapp.win32.hwnd, 1); + break; + case WM_TIMER: + _sapp_win32_timing_measure(); + _sapp_frame(); + #if defined(SOKOL_D3D11) + // present with DXGI_PRESENT_DO_NOT_WAIT + _sapp_d3d11_present(true); + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_swap_buffers(); + #endif + /* NOTE: resizing the swap-chain during resize leads to a substantial + memory spike (hundreds of megabytes for a few seconds). + + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + */ + break; + case WM_NCLBUTTONDOWN: + /* workaround for half-second pause when starting to move window + see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + */ + if (SendMessage(_sapp.win32.hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { + POINT point = { 0, 0 }; + if (GetCursorPos(&point)) { + ScreenToClient(_sapp.win32.hwnd, &point); + PostMessage(_sapp.win32.hwnd, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); + } + } + break; + case WM_DROPFILES: + _sapp_win32_files_dropped((HDROP)wParam); + break; + case WM_DISPLAYCHANGE: + // refresh rate might have changed + _sapp_timing_reset(&_sapp.timing); + break; + + default: + break; + } + } + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +_SOKOL_PRIVATE void _sapp_win32_create_window(void) { + WNDCLASSW wndclassw; + _sapp_clear(&wndclassw, sizeof(wndclassw)); + wndclassw.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclassw.lpfnWndProc = (WNDPROC) _sapp_win32_wndproc; + wndclassw.hInstance = GetModuleHandleW(NULL); + wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclassw.hIcon = LoadIcon(NULL, IDI_WINLOGO); + wndclassw.lpszClassName = L"SOKOLAPP"; + RegisterClassW(&wndclassw); + + /* NOTE: regardless whether fullscreen is requested or not, a regular + windowed-mode window will always be created first (however in hidden + mode, so that no windowed-mode window pops up before the fullscreen window) + */ + const DWORD win_ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + RECT rect = { 0, 0, 0, 0 }; + DWORD win_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX; + rect.right = (int) ((float)_sapp.window_width * _sapp.win32.dpi.window_scale); + rect.bottom = (int) ((float)_sapp.window_height * _sapp.win32.dpi.window_scale); + const bool use_default_width = 0 == _sapp.window_width; + const bool use_default_height = 0 == _sapp.window_height; + AdjustWindowRectEx(&rect, win_style, FALSE, win_ex_style); + const int win_width = rect.right - rect.left; + const int win_height = rect.bottom - rect.top; + _sapp.win32.in_create_window = true; + _sapp.win32.hwnd = CreateWindowExW( + win_ex_style, // dwExStyle + L"SOKOLAPP", // lpClassName + _sapp.window_title_wide, // lpWindowName + win_style, // dwStyle + CW_USEDEFAULT, // X + SW_HIDE, // Y (NOTE: CW_USEDEFAULT is not used for position here, but internally calls ShowWindow! + use_default_width ? CW_USEDEFAULT : win_width, // nWidth + use_default_height ? CW_USEDEFAULT : win_height, // nHeight (NOTE: if width is CW_USEDEFAULT, height is actually ignored) + NULL, // hWndParent + NULL, // hMenu + GetModuleHandle(NULL), // hInstance + NULL); // lParam + _sapp.win32.in_create_window = false; + _sapp.win32.dc = GetDC(_sapp.win32.hwnd); + _sapp.win32.hmonitor = MonitorFromWindow(_sapp.win32.hwnd, MONITOR_DEFAULTTONULL); + SOKOL_ASSERT(_sapp.win32.dc); + + /* this will get the actual windowed-mode window size, if fullscreen + is requested, the set_fullscreen function will then capture the + current window rectangle, which then might be used later to + restore the window position when switching back to windowed + */ + _sapp_win32_update_dimensions(); + if (_sapp.fullscreen) { + _sapp_win32_set_fullscreen(_sapp.fullscreen, SWP_HIDEWINDOW); + _sapp_win32_update_dimensions(); + } + ShowWindow(_sapp.win32.hwnd, SW_SHOW); + DragAcceptFiles(_sapp.win32.hwnd, 1); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_window(void) { + DestroyWindow(_sapp.win32.hwnd); _sapp.win32.hwnd = 0; + UnregisterClassW(L"SOKOLAPP", GetModuleHandleW(NULL)); +} + +_SOKOL_PRIVATE void _sapp_win32_destroy_icons(void) { + if (_sapp.win32.big_icon) { + DestroyIcon(_sapp.win32.big_icon); + _sapp.win32.big_icon = 0; + } + if (_sapp.win32.small_icon) { + DestroyIcon(_sapp.win32.small_icon); + _sapp.win32.small_icon = 0; + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_console(void) { + if (_sapp.desc.win32_console_create || _sapp.desc.win32_console_attach) { + BOOL con_valid = FALSE; + if (_sapp.desc.win32_console_create) { + con_valid = AllocConsole(); + } + else if (_sapp.desc.win32_console_attach) { + con_valid = AttachConsole(ATTACH_PARENT_PROCESS); + } + if (con_valid) { + FILE* res_fp = 0; + errno_t err; + err = freopen_s(&res_fp, "CON", "w", stdout); + (void)err; + err = freopen_s(&res_fp, "CON", "w", stderr); + (void)err; + } + } + if (_sapp.desc.win32_console_utf8) { + _sapp.win32.orig_codepage = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + } +} + +_SOKOL_PRIVATE void _sapp_win32_restore_console(void) { + if (_sapp.desc.win32_console_utf8) { + SetConsoleOutputCP(_sapp.win32.orig_codepage); + } +} + +_SOKOL_PRIVATE void _sapp_win32_init_dpi(void) { + + DECLARE_HANDLE(DPI_AWARENESS_CONTEXT_T); + typedef BOOL(WINAPI * SETPROCESSDPIAWARE_T)(void); + typedef bool (WINAPI * SETPROCESSDPIAWARENESSCONTEXT_T)(DPI_AWARENESS_CONTEXT_T); // since Windows 10, version 1703 + typedef HRESULT(WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); + typedef HRESULT(WINAPI * GETDPIFORMONITOR_T)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + SETPROCESSDPIAWARE_T fn_setprocessdpiaware = 0; + SETPROCESSDPIAWARENESS_T fn_setprocessdpiawareness = 0; + GETDPIFORMONITOR_T fn_getdpiformonitor = 0; + SETPROCESSDPIAWARENESSCONTEXT_T fn_setprocessdpiawarenesscontext =0; + + HINSTANCE user32 = LoadLibraryA("user32.dll"); + if (user32) { + fn_setprocessdpiaware = (SETPROCESSDPIAWARE_T)(void*) GetProcAddress(user32, "SetProcessDPIAware"); + fn_setprocessdpiawarenesscontext = (SETPROCESSDPIAWARENESSCONTEXT_T)(void*) GetProcAddress(user32, "SetProcessDpiAwarenessContext"); + } + HINSTANCE shcore = LoadLibraryA("shcore.dll"); + if (shcore) { + fn_setprocessdpiawareness = (SETPROCESSDPIAWARENESS_T)(void*) GetProcAddress(shcore, "SetProcessDpiAwareness"); + fn_getdpiformonitor = (GETDPIFORMONITOR_T)(void*) GetProcAddress(shcore, "GetDpiForMonitor"); + } + /* + NOTE on SetProcessDpiAware() vs SetProcessDpiAwareness() vs SetProcessDpiAwarenessContext(): + + These are different attempts to get DPI handling on Windows right, from oldest + to newest. SetProcessDpiAwarenessContext() is required for the new + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 method. + */ + if (fn_setprocessdpiawareness) { + if (_sapp.desc.high_dpi) { + /* app requests HighDPI rendering, first try the Win10 Creator Update per-monitor-dpi awareness, + if that fails, fall back to system-dpi-awareness + */ + _sapp.win32.dpi.aware = true; + DPI_AWARENESS_CONTEXT_T per_monitor_aware_v2 = (DPI_AWARENESS_CONTEXT_T)-4; + if (!(fn_setprocessdpiawarenesscontext && fn_setprocessdpiawarenesscontext(per_monitor_aware_v2))) { + // fallback to system-dpi-aware + fn_setprocessdpiawareness(PROCESS_SYSTEM_DPI_AWARE); + } + } + else { + /* if the app didn't request HighDPI rendering, let Windows do the upscaling */ + _sapp.win32.dpi.aware = false; + fn_setprocessdpiawareness(PROCESS_DPI_UNAWARE); + } + } + else if (fn_setprocessdpiaware) { + // fallback for Windows 7 + _sapp.win32.dpi.aware = true; + fn_setprocessdpiaware(); + } + /* get dpi scale factor for main monitor */ + if (fn_getdpiformonitor && _sapp.win32.dpi.aware) { + POINT pt = { 1, 1 }; + HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); + UINT dpix, dpiy; + HRESULT hr = fn_getdpiformonitor(hm, MDT_EFFECTIVE_DPI, &dpix, &dpiy); + _SOKOL_UNUSED(hr); + SOKOL_ASSERT(SUCCEEDED(hr)); + /* clamp window scale to an integer factor */ + _sapp.win32.dpi.window_scale = (float)dpix / 96.0f; + } + else { + _sapp.win32.dpi.window_scale = 1.0f; + } + if (_sapp.desc.high_dpi) { + _sapp.win32.dpi.content_scale = _sapp.win32.dpi.window_scale; + _sapp.win32.dpi.mouse_scale = 1.0f; + } + else { + _sapp.win32.dpi.content_scale = 1.0f; + _sapp.win32.dpi.mouse_scale = 1.0f / _sapp.win32.dpi.window_scale; + } + _sapp.dpi_scale = _sapp.win32.dpi.content_scale; + if (user32) { + FreeLibrary(user32); + } + if (shcore) { + FreeLibrary(shcore); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_set_clipboard_string(const char* str) { + SOKOL_ASSERT(str); + SOKOL_ASSERT(_sapp.win32.hwnd); + SOKOL_ASSERT(_sapp.clipboard.enabled && (_sapp.clipboard.buf_size > 0)); + + if (!OpenClipboard(_sapp.win32.hwnd)) { + return false; + } + + HANDLE object = 0; + wchar_t* wchar_buf = 0; + + const SIZE_T wchar_buf_size = (SIZE_T)_sapp.clipboard.buf_size * sizeof(wchar_t); + object = GlobalAlloc(GMEM_MOVEABLE, wchar_buf_size); + if (NULL == object) { + goto error; + } + wchar_buf = (wchar_t*) GlobalLock(object); + if (NULL == wchar_buf) { + goto error; + } + if (!_sapp_win32_utf8_to_wide(str, wchar_buf, (int)wchar_buf_size)) { + goto error; + } + GlobalUnlock(object); + wchar_buf = 0; + EmptyClipboard(); + // NOTE: when successful, SetClipboardData() takes ownership of memory object! + if (NULL == SetClipboardData(CF_UNICODETEXT, object)) { + goto error; + } + CloseClipboard(); + return true; + +error: + if (wchar_buf) { + GlobalUnlock(object); + } + if (object) { + GlobalFree(object); + } + CloseClipboard(); + return false; +} + +_SOKOL_PRIVATE const char* _sapp_win32_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + SOKOL_ASSERT(_sapp.win32.hwnd); + if (!OpenClipboard(_sapp.win32.hwnd)) { + /* silently ignore any errors and just return the current + content of the local clipboard buffer + */ + return _sapp.clipboard.buffer; + } + HANDLE object = GetClipboardData(CF_UNICODETEXT); + if (!object) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + const wchar_t* wchar_buf = (const wchar_t*) GlobalLock(object); + if (!wchar_buf) { + CloseClipboard(); + return _sapp.clipboard.buffer; + } + if (!_sapp_win32_wide_to_utf8(wchar_buf, _sapp.clipboard.buffer, _sapp.clipboard.buf_size)) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + } + GlobalUnlock(object); + CloseClipboard(); + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_win32_update_window_title(void) { + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + SetWindowTextW(_sapp.win32.hwnd, _sapp.window_title_wide); +} + +_SOKOL_PRIVATE HICON _sapp_win32_create_icon_from_image(const sapp_image_desc* desc) { + BITMAPV5HEADER bi; + _sapp_clear(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = desc->width; + bi.bV5Height = -desc->height; // NOTE the '-' here to indicate that origin is top-left + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + uint8_t* target = 0; + const uint8_t* source = (const uint8_t*)desc->pixels.ptr; + + HDC dc = GetDC(NULL); + HBITMAP color = CreateDIBSection(dc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&target, NULL, (DWORD)0); + ReleaseDC(NULL, dc); + if (0 == color) { + return NULL; + } + SOKOL_ASSERT(target); + + HBITMAP mask = CreateBitmap(desc->width, desc->height, 1, 1, NULL); + if (0 == mask) { + DeleteObject(color); + return NULL; + } + + for (int i = 0; i < (desc->width*desc->height); i++) { + target[0] = source[2]; + target[1] = source[1]; + target[2] = source[0]; + target[3] = source[3]; + target += 4; + source += 4; + } + + ICONINFO icon_info; + _sapp_clear(&icon_info, sizeof(icon_info)); + icon_info.fIcon = true; + icon_info.xHotspot = 0; + icon_info.yHotspot = 0; + icon_info.hbmMask = mask; + icon_info.hbmColor = color; + HICON icon_handle = CreateIconIndirect(&icon_info); + DeleteObject(color); + DeleteObject(mask); + + return icon_handle; +} + +_SOKOL_PRIVATE void _sapp_win32_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + + int big_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); + int sml_img_index = _sapp_image_bestmatch(icon_desc->images, num_images, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + HICON big_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[big_img_index]); + HICON sml_icon = _sapp_win32_create_icon_from_image(&icon_desc->images[sml_img_index]); + + // if icon creation or lookup has failed for some reason, leave the currently set icon untouched + if (0 != big_icon) { + SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_BIG, (LPARAM) big_icon); + if (0 != _sapp.win32.big_icon) { + DestroyIcon(_sapp.win32.big_icon); + } + _sapp.win32.big_icon = big_icon; + } + if (0 != sml_icon) { + SendMessage(_sapp.win32.hwnd, WM_SETICON, ICON_SMALL, (LPARAM) sml_icon); + if (0 != _sapp.win32.small_icon) { + DestroyIcon(_sapp.win32.small_icon); + } + _sapp.win32.small_icon = sml_icon; + } +} + +/* don't laugh, but this seems to be the easiest and most robust + way to check if we're running on Win10 + + From: https://github.com/videolan/vlc/blob/232fb13b0d6110c4d1b683cde24cf9a7f2c5c2ea/modules/video_output/win32/d3d11_swapchain.c#L263 +*/ +_SOKOL_PRIVATE bool _sapp_win32_is_win10_or_greater(void) { + HMODULE h = GetModuleHandleW(L"kernel32.dll"); + if (NULL != h) { + return (NULL != GetProcAddress(h, "GetSystemCpuSetInformation")); + } + else { + return false; + } +} + +_SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { + _sapp_init_state(desc); + _sapp_win32_init_console(); + _sapp.win32.is_win10_or_greater = _sapp_win32_is_win10_or_greater(); + _sapp_win32_init_keytable(); + _sapp_win32_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); + _sapp_win32_init_dpi(); + _sapp_win32_init_cursors(); + _sapp_win32_create_window(); + sapp_set_icon(&desc->icon); + #if defined(SOKOL_D3D11) + _sapp_d3d11_create_device_and_swapchain(); + _sapp_d3d11_create_default_render_target(); + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_init(); + _sapp_wgl_load_extensions(); + _sapp_wgl_create_context(); + #endif + _sapp.valid = true; + + bool done = false; + while (!(done || _sapp.quit_ordered)) { + _sapp_win32_timing_measure(); + MSG msg; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + if (WM_QUIT == msg.message) { + done = true; + continue; + } + else { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + _sapp_frame(); + #if defined(SOKOL_D3D11) + _sapp_d3d11_present(false); + if (IsIconic(_sapp.win32.hwnd)) { + Sleep((DWORD)(16 * _sapp.swap_interval)); + } + #endif + #if defined(SOKOL_GLCORE) + _sapp_wgl_swap_buffers(); + #endif + /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ + if (_sapp_win32_update_dimensions()) { + #if defined(SOKOL_D3D11) + _sapp_d3d11_resize_default_render_target(); + #endif + _sapp_win32_app_event(SAPP_EVENTTYPE_RESIZED); + } + /* check if the window monitor has changed, need to reset timing because + the new monitor might have a different refresh rate + */ + if (_sapp_win32_update_monitor()) { + _sapp_timing_reset(&_sapp.timing); + } + if (_sapp.quit_requested) { + PostMessage(_sapp.win32.hwnd, WM_CLOSE, 0, 0); + } + // update mouse-lock state + _sapp_win32_update_mouse_lock(); + } + _sapp_call_cleanup(); + + #if defined(SOKOL_D3D11) + _sapp_d3d11_destroy_default_render_target(); + _sapp_d3d11_destroy_device_and_swapchain(); + #elif defined(SOKOL_GLCORE) + _sapp_wgl_destroy_context(); + _sapp_wgl_shutdown(); + #endif + _sapp_win32_destroy_window(); + _sapp_win32_destroy_icons(); + _sapp_win32_restore_console(); + _sapp_discard_state(); +} + +_SOKOL_PRIVATE char** _sapp_win32_command_line_to_utf8_argv(LPWSTR w_command_line, int* o_argc) { + int argc = 0; + char** argv = 0; + char* args; + + LPWSTR* w_argv = CommandLineToArgvW(w_command_line, &argc); + if (w_argv == NULL) { + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! + } else { + size_t size = wcslen(w_command_line) * 4; + argv = (char**) _sapp_malloc_clear(((size_t)argc + 1) * sizeof(char*) + size); + SOKOL_ASSERT(argv); + args = (char*) &argv[argc + 1]; + int n; + for (int i = 0; i < argc; ++i) { + n = WideCharToMultiByte(CP_UTF8, 0, w_argv[i], -1, args, (int)size, NULL, NULL); + if (n == 0) { + // FIXME: chicken egg problem, can't report errors before sokol_main() is called! + break; + } + argv[i] = args; + size -= (size_t)n; + args += n; + } + LocalFree(w_argv); + } + *o_argc = argc; + return argv; +} + +#if !defined(SOKOL_NO_ENTRY) +#if defined(SOKOL_WIN32_FORCE_MAIN) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_win32_run(&desc); + return 0; +} +#endif /* SOKOL_WIN32_FORCE_MAIN */ +#if defined(SOKOL_WIN32_FORCE_WINMAIN) || !defined(SOKOL_WIN32_FORCE_MAIN) +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + _SOKOL_UNUSED(hInstance); + _SOKOL_UNUSED(hPrevInstance); + _SOKOL_UNUSED(lpCmdLine); + _SOKOL_UNUSED(nCmdShow); + int argc_utf8 = 0; + char** argv_utf8 = _sapp_win32_command_line_to_utf8_argv(GetCommandLineW(), &argc_utf8); + sapp_desc desc = sokol_main(argc_utf8, argv_utf8); + _sapp_win32_run(&desc); + _sapp_free(argv_utf8); + return 0; +} +#endif /* SOKOL_WIN32_FORCE_WINMAIN */ +#endif /* SOKOL_NO_ENTRY */ + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif /* _SAPP_WIN32 */ + +// █████ ███ ██ ██████ ██████ ██████ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ████ ██████ ██ ██ ██████ ██ ██████ +// +// >>android +#if defined(_SAPP_ANDROID) + +/* android loop thread */ +_SOKOL_PRIVATE bool _sapp_android_init_egl(void) { + SOKOL_ASSERT(_sapp.android.display == EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context == EGL_NO_CONTEXT); + + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return false; + } + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { + return false; + } + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint cfg_attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 0, + EGL_NONE, + }; + EGLConfig available_cfgs[32]; + EGLint cfg_count; + eglChooseConfig(display, cfg_attributes, available_cfgs, 32, &cfg_count); + SOKOL_ASSERT(cfg_count > 0); + SOKOL_ASSERT(cfg_count <= 32); + + /* find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec */ + EGLConfig config; + bool exact_cfg_found = false; + for (int i = 0; i < cfg_count; ++i) { + EGLConfig c = available_cfgs[i]; + EGLint r, g, b, a, d; + if (eglGetConfigAttrib(display, c, EGL_RED_SIZE, &r) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_GREEN_SIZE, &g) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_BLUE_SIZE, &b) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_ALPHA_SIZE, &a) == EGL_TRUE && + eglGetConfigAttrib(display, c, EGL_DEPTH_SIZE, &d) == EGL_TRUE && + r == 8 && g == 8 && b == 8 && (alpha_size == 0 || a == alpha_size) && d == 16) { + exact_cfg_found = true; + config = c; + break; + } + } + if (!exact_cfg_found) { + config = available_cfgs[0]; + } + + EGLint ctx_attributes[] = { + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + EGL_NONE, + }; + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctx_attributes); + if (context == EGL_NO_CONTEXT) { + return false; + } + + _sapp.android.config = config; + _sapp.android.display = display; + _sapp.android.context = context; + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl(void) { + if (_sapp.android.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } + if (_sapp.android.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.android.display, _sapp.android.context); + _sapp.android.context = EGL_NO_CONTEXT; + } + eglTerminate(_sapp.android.display); + _sapp.android.display = EGL_NO_DISPLAY; + } +} + +_SOKOL_PRIVATE bool _sapp_android_init_egl_surface(ANativeWindow* window) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface == EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + /* TODO: set window flags */ + /* ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); */ + + /* create egl surface and make it current */ + EGLSurface surface = eglCreateWindowSurface(_sapp.android.display, _sapp.android.config, window, NULL); + if (surface == EGL_NO_SURFACE) { + return false; + } + if (eglMakeCurrent(_sapp.android.display, surface, surface, _sapp.android.context) == EGL_FALSE) { + return false; + } + _sapp.android.surface = surface; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + return true; +} + +_SOKOL_PRIVATE void _sapp_android_cleanup_egl_surface(void) { + if (_sapp.android.display == EGL_NO_DISPLAY) { + return; + } + eglMakeCurrent(_sapp.android.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (_sapp.android.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.android.display, _sapp.android.surface); + _sapp.android.surface = EGL_NO_SURFACE; + } +} + +_SOKOL_PRIVATE void _sapp_android_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_android_update_dimensions(ANativeWindow* window, bool force_update) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + SOKOL_ASSERT(window); + + const int32_t win_w = ANativeWindow_getWidth(window); + const int32_t win_h = ANativeWindow_getHeight(window); + SOKOL_ASSERT(win_w >= 0 && win_h >= 0); + const bool win_changed = (win_w != _sapp.window_width) || (win_h != _sapp.window_height); + _sapp.window_width = win_w; + _sapp.window_height = win_h; + if (win_changed || force_update) { + if (!_sapp.desc.high_dpi) { + const int32_t buf_w = win_w / 2; + const int32_t buf_h = win_h / 2; + EGLint format; + EGLBoolean egl_result = eglGetConfigAttrib(_sapp.android.display, _sapp.android.config, EGL_NATIVE_VISUAL_ID, &format); + SOKOL_ASSERT(egl_result == EGL_TRUE); _SOKOL_UNUSED(egl_result); + /* NOTE: calling ANativeWindow_setBuffersGeometry() with the same dimensions + as the ANativeWindow size results in weird display artefacts, that's + why it's only called when the buffer geometry is different from + the window size + */ + int32_t result = ANativeWindow_setBuffersGeometry(window, buf_w, buf_h, format); + SOKOL_ASSERT(result == 0); _SOKOL_UNUSED(result); + } + } + + /* query surface size */ + EGLint fb_w, fb_h; + EGLBoolean egl_result_w = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_WIDTH, &fb_w); + EGLBoolean egl_result_h = eglQuerySurface(_sapp.android.display, _sapp.android.surface, EGL_HEIGHT, &fb_h); + SOKOL_ASSERT(egl_result_w == EGL_TRUE); _SOKOL_UNUSED(egl_result_w); + SOKOL_ASSERT(egl_result_h == EGL_TRUE); _SOKOL_UNUSED(egl_result_h); + const bool fb_changed = (fb_w != _sapp.framebuffer_width) || (fb_h != _sapp.framebuffer_height); + _sapp.framebuffer_width = fb_w; + _sapp.framebuffer_height = fb_h; + _sapp.dpi_scale = (float)_sapp.framebuffer_width / (float)_sapp.window_width; + if (win_changed || fb_changed || force_update) { + if (!_sapp.first_frame) { + _sapp_android_app_event(SAPP_EVENTTYPE_RESIZED); + } + } +} + +_SOKOL_PRIVATE void _sapp_android_cleanup(void) { + if (_sapp.android.surface != EGL_NO_SURFACE) { + /* egl context is bound, cleanup gracefully */ + if (_sapp.init_called && !_sapp.cleanup_called) { + _sapp_call_cleanup(); + } + } + /* always try to cleanup by destroying egl context */ + _sapp_android_cleanup_egl(); +} + +_SOKOL_PRIVATE void _sapp_android_shutdown(void) { + /* try to cleanup while we still have a surface and can call cleanup_cb() */ + _sapp_android_cleanup(); + /* request exit */ + ANativeActivity_finish(_sapp.android.activity); +} + +_SOKOL_PRIVATE void _sapp_android_frame(void) { + SOKOL_ASSERT(_sapp.android.display != EGL_NO_DISPLAY); + SOKOL_ASSERT(_sapp.android.context != EGL_NO_CONTEXT); + SOKOL_ASSERT(_sapp.android.surface != EGL_NO_SURFACE); + _sapp_timing_measure(&_sapp.timing); + _sapp_android_update_dimensions(_sapp.android.current.window, false); + _sapp_frame(); + eglSwapBuffers(_sapp.android.display, _sapp.android.surface); +} + +_SOKOL_PRIVATE bool _sapp_android_touch_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_MOTION) { + return false; + } + if (!_sapp_events_enabled()) { + return false; + } + int32_t action_idx = AMotionEvent_getAction(e); + int32_t action = action_idx & AMOTION_EVENT_ACTION_MASK; + sapp_event_type type = SAPP_EVENTTYPE_INVALID; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + type = SAPP_EVENTTYPE_TOUCHES_BEGAN; + break; + case AMOTION_EVENT_ACTION_MOVE: + type = SAPP_EVENTTYPE_TOUCHES_MOVED; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + type = SAPP_EVENTTYPE_TOUCHES_ENDED; + break; + case AMOTION_EVENT_ACTION_CANCEL: + type = SAPP_EVENTTYPE_TOUCHES_CANCELLED; + break; + default: + break; + } + if (type == SAPP_EVENTTYPE_INVALID) { + return false; + } + int32_t idx = action_idx >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + _sapp_init_event(type); + _sapp.event.num_touches = (int)AMotionEvent_getPointerCount(e); + if (_sapp.event.num_touches > SAPP_MAX_TOUCHPOINTS) { + _sapp.event.num_touches = SAPP_MAX_TOUCHPOINTS; + } + for (int32_t i = 0; i < _sapp.event.num_touches; i++) { + sapp_touchpoint* dst = &_sapp.event.touches[i]; + dst->identifier = (uintptr_t)AMotionEvent_getPointerId(e, (size_t)i); + dst->pos_x = (AMotionEvent_getX(e, (size_t)i) / _sapp.window_width) * _sapp.framebuffer_width; + dst->pos_y = (AMotionEvent_getY(e, (size_t)i) / _sapp.window_height) * _sapp.framebuffer_height; + dst->android_tooltype = (sapp_android_tooltype) AMotionEvent_getToolType(e, (size_t)i); + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { + dst->changed = (i == idx); + } else { + dst->changed = true; + } + } + _sapp_call_event(&_sapp.event); + return true; +} + +_SOKOL_PRIVATE bool _sapp_android_key_event(const AInputEvent* e) { + if (AInputEvent_getType(e) != AINPUT_EVENT_TYPE_KEY) { + return false; + } + if (AKeyEvent_getKeyCode(e) == AKEYCODE_BACK) { + /* FIXME: this should be hooked into a "really quit?" mechanism + so the app can ask the user for confirmation, this is currently + generally missing in sokol_app.h + */ + _sapp_android_shutdown(); + return true; + } + return false; +} + +_SOKOL_PRIVATE int _sapp_android_input_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(fd); + _SOKOL_UNUSED(data); + if ((events & ALOOPER_EVENT_INPUT) == 0) { + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB); + return 1; + } + SOKOL_ASSERT(_sapp.android.current.input); + AInputEvent* event = NULL; + while (AInputQueue_getEvent(_sapp.android.current.input, &event) >= 0) { + if (AInputQueue_preDispatchEvent(_sapp.android.current.input, event) != 0) { + continue; + } + int32_t handled = 0; + if (_sapp_android_touch_event(event) || _sapp_android_key_event(event)) { + handled = 1; + } + AInputQueue_finishEvent(_sapp.android.current.input, event, handled); + } + return 1; +} + +_SOKOL_PRIVATE int _sapp_android_main_cb(int fd, int events, void* data) { + _SOKOL_UNUSED(data); + if ((events & ALOOPER_EVENT_INPUT) == 0) { + _SAPP_ERROR(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB); + return 1; + } + + _sapp_android_msg_t msg; + if (read(fd, &msg, sizeof(msg)) != sizeof(msg)) { + _SAPP_ERROR(ANDROID_READ_MSG_FAILED); + return 1; + } + + pthread_mutex_lock(&_sapp.android.pt.mutex); + switch (msg) { + case _SOKOL_ANDROID_MSG_CREATE: + { + _SAPP_INFO(ANDROID_MSG_CREATE); + SOKOL_ASSERT(!_sapp.valid); + bool result = _sapp_android_init_egl(); + SOKOL_ASSERT(result); _SOKOL_UNUSED(result); + _sapp.valid = true; + _sapp.android.has_created = true; + } + break; + case _SOKOL_ANDROID_MSG_RESUME: + _SAPP_INFO(ANDROID_MSG_RESUME); + _sapp.android.has_resumed = true; + _sapp_android_app_event(SAPP_EVENTTYPE_RESUMED); + break; + case _SOKOL_ANDROID_MSG_PAUSE: + _SAPP_INFO(ANDROID_MSG_PAUSE); + _sapp.android.has_resumed = false; + _sapp_android_app_event(SAPP_EVENTTYPE_SUSPENDED); + break; + case _SOKOL_ANDROID_MSG_FOCUS: + _SAPP_INFO(ANDROID_MSG_FOCUS); + _sapp.android.has_focus = true; + break; + case _SOKOL_ANDROID_MSG_NO_FOCUS: + _SAPP_INFO(ANDROID_MSG_NO_FOCUS); + _sapp.android.has_focus = false; + break; + case _SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW: + _SAPP_INFO(ANDROID_MSG_SET_NATIVE_WINDOW); + if (_sapp.android.current.window != _sapp.android.pending.window) { + if (_sapp.android.current.window != NULL) { + _sapp_android_cleanup_egl_surface(); + } + if (_sapp.android.pending.window != NULL) { + if (_sapp_android_init_egl_surface(_sapp.android.pending.window)) { + _sapp_android_update_dimensions(_sapp.android.pending.window, true); + } else { + _sapp_android_shutdown(); + } + } + } + _sapp.android.current.window = _sapp.android.pending.window; + break; + case _SOKOL_ANDROID_MSG_SET_INPUT_QUEUE: + _SAPP_INFO(ANDROID_MSG_SET_INPUT_QUEUE); + if (_sapp.android.current.input != _sapp.android.pending.input) { + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + if (_sapp.android.pending.input != NULL) { + AInputQueue_attachLooper( + _sapp.android.pending.input, + _sapp.android.looper, + ALOOPER_POLL_CALLBACK, + _sapp_android_input_cb, + NULL); /* data */ + } + } + _sapp.android.current.input = _sapp.android.pending.input; + break; + case _SOKOL_ANDROID_MSG_DESTROY: + _SAPP_INFO(ANDROID_MSG_DESTROY); + _sapp_android_cleanup(); + _sapp.valid = false; + _sapp.android.is_thread_stopping = true; + break; + default: + _SAPP_WARN(ANDROID_UNKNOWN_MSG); + break; + } + pthread_cond_broadcast(&_sapp.android.pt.cond); /* signal "received" */ + pthread_mutex_unlock(&_sapp.android.pt.mutex); + return 1; +} + +_SOKOL_PRIVATE bool _sapp_android_should_update(void) { + bool is_in_front = _sapp.android.has_resumed && _sapp.android.has_focus; + bool has_surface = _sapp.android.surface != EGL_NO_SURFACE; + return is_in_front && has_surface; +} + +_SOKOL_PRIVATE void _sapp_android_show_keyboard(bool shown) { + SOKOL_ASSERT(_sapp.valid); + /* This seems to be broken in the NDK, but there is (a very cumbersome) workaround... */ + if (shown) { + ANativeActivity_showSoftInput(_sapp.android.activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED); + } else { + ANativeActivity_hideSoftInput(_sapp.android.activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS); + } +} + +_SOKOL_PRIVATE void* _sapp_android_loop(void* arg) { + _SOKOL_UNUSED(arg); + _SAPP_INFO(ANDROID_LOOP_THREAD_STARTED); + + _sapp.android.looper = ALooper_prepare(0 /* or ALOOPER_PREPARE_ALLOW_NON_CALLBACKS*/); + ALooper_addFd(_sapp.android.looper, + _sapp.android.pt.read_from_main_fd, + ALOOPER_POLL_CALLBACK, + ALOOPER_EVENT_INPUT, + _sapp_android_main_cb, + NULL); /* data */ + + /* signal start to main thread */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_started = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* main loop */ + while (!_sapp.android.is_thread_stopping) { + /* sokol frame */ + if (_sapp_android_should_update()) { + _sapp_android_frame(); + } + + /* process all events (or stop early if app is requested to quit) */ + bool process_events = true; + while (process_events && !_sapp.android.is_thread_stopping) { + bool block_until_event = !_sapp.android.is_thread_stopping && !_sapp_android_should_update(); + process_events = ALooper_pollOnce(block_until_event ? -1 : 0, NULL, NULL, NULL) == ALOOPER_POLL_CALLBACK; + } + } + + /* cleanup thread */ + if (_sapp.android.current.input != NULL) { + AInputQueue_detachLooper(_sapp.android.current.input); + } + + /* the following causes heap corruption on exit, why?? + ALooper_removeFd(_sapp.android.looper, _sapp.android.pt.read_from_main_fd); + ALooper_release(_sapp.android.looper);*/ + + /* signal "destroyed" */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.is_thread_stopped = true; + pthread_cond_broadcast(&_sapp.android.pt.cond); + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + _SAPP_INFO(ANDROID_LOOP_THREAD_DONE); + return NULL; +} + +/* android main/ui thread */ +_SOKOL_PRIVATE void _sapp_android_msg(_sapp_android_msg_t msg) { + if (write(_sapp.android.pt.write_from_main_fd, &msg, sizeof(msg)) != sizeof(msg)) { + _SAPP_ERROR(ANDROID_WRITE_MSG_FAILED); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_start(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTART); +} + +_SOKOL_PRIVATE void _sapp_android_on_resume(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONRESUME); + _sapp_android_msg(_SOKOL_ANDROID_MSG_RESUME); +} + +_SOKOL_PRIVATE void* _sapp_android_on_save_instance_state(ANativeActivity* activity, size_t* out_size) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSAVEINSTANCESTATE); + *out_size = 0; + return NULL; +} + +_SOKOL_PRIVATE void _sapp_android_on_window_focus_changed(ANativeActivity* activity, int has_focus) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONWINDOWFOCUSCHANGED); + if (has_focus) { + _sapp_android_msg(_SOKOL_ANDROID_MSG_FOCUS); + } else { + _sapp_android_msg(_SOKOL_ANDROID_MSG_NO_FOCUS); + } +} + +_SOKOL_PRIVATE void _sapp_android_on_pause(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONPAUSE); + _sapp_android_msg(_SOKOL_ANDROID_MSG_PAUSE); +} + +_SOKOL_PRIVATE void _sapp_android_on_stop(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONSTOP); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_native_window(ANativeWindow* window) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.window = window; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_NATIVE_WINDOW); + while (_sapp.android.current.window != window) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_created(ANativeActivity* activity, ANativeWindow* window) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWCREATED); + _sapp_android_msg_set_native_window(window); +} + +_SOKOL_PRIVATE void _sapp_android_on_native_window_destroyed(ANativeActivity* activity, ANativeWindow* window) { + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(window); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONNATIVEWINDOWDESTROYED); + _sapp_android_msg_set_native_window(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_msg_set_input_queue(AInputQueue* input) { + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp.android.pending.input = input; + _sapp_android_msg(_SOKOL_ANDROID_MSG_SET_INPUT_QUEUE); + while (_sapp.android.current.input != input) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUECREATED); + _sapp_android_msg_set_input_queue(queue); +} + +_SOKOL_PRIVATE void _sapp_android_on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) { + _SOKOL_UNUSED(activity); + _SOKOL_UNUSED(queue); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONINPUTQUEUEDESTROYED); + _sapp_android_msg_set_input_queue(NULL); +} + +_SOKOL_PRIVATE void _sapp_android_on_config_changed(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCONFIGURATIONCHANGED); + /* see android:configChanges in manifest */ +} + +_SOKOL_PRIVATE void _sapp_android_on_low_memory(ANativeActivity* activity) { + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONLOWMEMORY); +} + +_SOKOL_PRIVATE void _sapp_android_on_destroy(ANativeActivity* activity) { + /* + * For some reason even an empty app using nativeactivity.h will crash (WIN DEATH) + * on my device (Moto X 2nd gen) when the app is removed from the task view + * (TaskStackView: onTaskViewDismissed). + * + * However, if ANativeActivity_finish() is explicitly called from for example + * _sapp_android_on_stop(), the crash disappears. Is this a bug in NativeActivity? + */ + _SOKOL_UNUSED(activity); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONDESTROY); + + /* send destroy msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_DESTROY); + while (!_sapp.android.is_thread_stopped) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* clean up main thread */ + pthread_cond_destroy(&_sapp.android.pt.cond); + pthread_mutex_destroy(&_sapp.android.pt.mutex); + + close(_sapp.android.pt.read_from_main_fd); + close(_sapp.android.pt.write_from_main_fd); + + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_DONE); + + /* this is a bit naughty, but causes a clean restart of the app (static globals are reset) */ + exit(0); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state, size_t saved_state_size) { + _SOKOL_UNUSED(saved_state); + _SOKOL_UNUSED(saved_state_size); + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_ONCREATE); + + // the NativeActity pointer needs to be available inside sokol_main() + // (see https://github.com/floooh/sokol/issues/708), however _sapp_init_state() + // will clear the global _sapp_t struct, so we need to initialize the native + // activity pointer twice, once before sokol_main() and once after _sapp_init_state() + _sapp_clear(&_sapp, sizeof(_sapp)); + _sapp.android.activity = activity; + sapp_desc desc = sokol_main(0, NULL); + _sapp_init_state(&desc); + _sapp.android.activity = activity; + + int pipe_fd[2]; + if (pipe(pipe_fd) != 0) { + _SAPP_ERROR(ANDROID_CREATE_THREAD_PIPE_FAILED); + return; + } + _sapp.android.pt.read_from_main_fd = pipe_fd[0]; + _sapp.android.pt.write_from_main_fd = pipe_fd[1]; + + pthread_mutex_init(&_sapp.android.pt.mutex, NULL); + pthread_cond_init(&_sapp.android.pt.cond, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&_sapp.android.pt.thread, &attr, _sapp_android_loop, 0); + pthread_attr_destroy(&attr); + + /* wait until main loop has started */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + while (!_sapp.android.is_thread_started) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* send create msg */ + pthread_mutex_lock(&_sapp.android.pt.mutex); + _sapp_android_msg(_SOKOL_ANDROID_MSG_CREATE); + while (!_sapp.android.has_created) { + pthread_cond_wait(&_sapp.android.pt.cond, &_sapp.android.pt.mutex); + } + pthread_mutex_unlock(&_sapp.android.pt.mutex); + + /* register for callbacks */ + activity->callbacks->onStart = _sapp_android_on_start; + activity->callbacks->onResume = _sapp_android_on_resume; + activity->callbacks->onSaveInstanceState = _sapp_android_on_save_instance_state; + activity->callbacks->onWindowFocusChanged = _sapp_android_on_window_focus_changed; + activity->callbacks->onPause = _sapp_android_on_pause; + activity->callbacks->onStop = _sapp_android_on_stop; + activity->callbacks->onDestroy = _sapp_android_on_destroy; + activity->callbacks->onNativeWindowCreated = _sapp_android_on_native_window_created; + /* activity->callbacks->onNativeWindowResized = _sapp_android_on_native_window_resized; */ + /* activity->callbacks->onNativeWindowRedrawNeeded = _sapp_android_on_native_window_redraw_needed; */ + activity->callbacks->onNativeWindowDestroyed = _sapp_android_on_native_window_destroyed; + activity->callbacks->onInputQueueCreated = _sapp_android_on_input_queue_created; + activity->callbacks->onInputQueueDestroyed = _sapp_android_on_input_queue_destroyed; + /* activity->callbacks->onContentRectChanged = _sapp_android_on_content_rect_changed; */ + activity->callbacks->onConfigurationChanged = _sapp_android_on_config_changed; + activity->callbacks->onLowMemory = _sapp_android_on_low_memory; + + _SAPP_INFO(ANDROID_NATIVE_ACTIVITY_CREATE_SUCCESS); + + /* NOT A BUG: do NOT call sapp_discard_state() */ +} + +#endif /* _SAPP_ANDROID */ + +// ██ ██ ███ ██ ██ ██ ██ ██ +// ██ ██ ████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ████ ██████ ██ ██ +// +// >>linux +#if defined(_SAPP_LINUX) + +/* see GLFW's xkb_unicode.c */ +static const struct _sapp_x11_codepair { + uint16_t keysym; + uint16_t ucs; +} _sapp_x11_keysymtab[] = { + { 0x01a1, 0x0104 }, + { 0x01a2, 0x02d8 }, + { 0x01a3, 0x0141 }, + { 0x01a5, 0x013d }, + { 0x01a6, 0x015a }, + { 0x01a9, 0x0160 }, + { 0x01aa, 0x015e }, + { 0x01ab, 0x0164 }, + { 0x01ac, 0x0179 }, + { 0x01ae, 0x017d }, + { 0x01af, 0x017b }, + { 0x01b1, 0x0105 }, + { 0x01b2, 0x02db }, + { 0x01b3, 0x0142 }, + { 0x01b5, 0x013e }, + { 0x01b6, 0x015b }, + { 0x01b7, 0x02c7 }, + { 0x01b9, 0x0161 }, + { 0x01ba, 0x015f }, + { 0x01bb, 0x0165 }, + { 0x01bc, 0x017a }, + { 0x01bd, 0x02dd }, + { 0x01be, 0x017e }, + { 0x01bf, 0x017c }, + { 0x01c0, 0x0154 }, + { 0x01c3, 0x0102 }, + { 0x01c5, 0x0139 }, + { 0x01c6, 0x0106 }, + { 0x01c8, 0x010c }, + { 0x01ca, 0x0118 }, + { 0x01cc, 0x011a }, + { 0x01cf, 0x010e }, + { 0x01d0, 0x0110 }, + { 0x01d1, 0x0143 }, + { 0x01d2, 0x0147 }, + { 0x01d5, 0x0150 }, + { 0x01d8, 0x0158 }, + { 0x01d9, 0x016e }, + { 0x01db, 0x0170 }, + { 0x01de, 0x0162 }, + { 0x01e0, 0x0155 }, + { 0x01e3, 0x0103 }, + { 0x01e5, 0x013a }, + { 0x01e6, 0x0107 }, + { 0x01e8, 0x010d }, + { 0x01ea, 0x0119 }, + { 0x01ec, 0x011b }, + { 0x01ef, 0x010f }, + { 0x01f0, 0x0111 }, + { 0x01f1, 0x0144 }, + { 0x01f2, 0x0148 }, + { 0x01f5, 0x0151 }, + { 0x01f8, 0x0159 }, + { 0x01f9, 0x016f }, + { 0x01fb, 0x0171 }, + { 0x01fe, 0x0163 }, + { 0x01ff, 0x02d9 }, + { 0x02a1, 0x0126 }, + { 0x02a6, 0x0124 }, + { 0x02a9, 0x0130 }, + { 0x02ab, 0x011e }, + { 0x02ac, 0x0134 }, + { 0x02b1, 0x0127 }, + { 0x02b6, 0x0125 }, + { 0x02b9, 0x0131 }, + { 0x02bb, 0x011f }, + { 0x02bc, 0x0135 }, + { 0x02c5, 0x010a }, + { 0x02c6, 0x0108 }, + { 0x02d5, 0x0120 }, + { 0x02d8, 0x011c }, + { 0x02dd, 0x016c }, + { 0x02de, 0x015c }, + { 0x02e5, 0x010b }, + { 0x02e6, 0x0109 }, + { 0x02f5, 0x0121 }, + { 0x02f8, 0x011d }, + { 0x02fd, 0x016d }, + { 0x02fe, 0x015d }, + { 0x03a2, 0x0138 }, + { 0x03a3, 0x0156 }, + { 0x03a5, 0x0128 }, + { 0x03a6, 0x013b }, + { 0x03aa, 0x0112 }, + { 0x03ab, 0x0122 }, + { 0x03ac, 0x0166 }, + { 0x03b3, 0x0157 }, + { 0x03b5, 0x0129 }, + { 0x03b6, 0x013c }, + { 0x03ba, 0x0113 }, + { 0x03bb, 0x0123 }, + { 0x03bc, 0x0167 }, + { 0x03bd, 0x014a }, + { 0x03bf, 0x014b }, + { 0x03c0, 0x0100 }, + { 0x03c7, 0x012e }, + { 0x03cc, 0x0116 }, + { 0x03cf, 0x012a }, + { 0x03d1, 0x0145 }, + { 0x03d2, 0x014c }, + { 0x03d3, 0x0136 }, + { 0x03d9, 0x0172 }, + { 0x03dd, 0x0168 }, + { 0x03de, 0x016a }, + { 0x03e0, 0x0101 }, + { 0x03e7, 0x012f }, + { 0x03ec, 0x0117 }, + { 0x03ef, 0x012b }, + { 0x03f1, 0x0146 }, + { 0x03f2, 0x014d }, + { 0x03f3, 0x0137 }, + { 0x03f9, 0x0173 }, + { 0x03fd, 0x0169 }, + { 0x03fe, 0x016b }, + { 0x047e, 0x203e }, + { 0x04a1, 0x3002 }, + { 0x04a2, 0x300c }, + { 0x04a3, 0x300d }, + { 0x04a4, 0x3001 }, + { 0x04a5, 0x30fb }, + { 0x04a6, 0x30f2 }, + { 0x04a7, 0x30a1 }, + { 0x04a8, 0x30a3 }, + { 0x04a9, 0x30a5 }, + { 0x04aa, 0x30a7 }, + { 0x04ab, 0x30a9 }, + { 0x04ac, 0x30e3 }, + { 0x04ad, 0x30e5 }, + { 0x04ae, 0x30e7 }, + { 0x04af, 0x30c3 }, + { 0x04b0, 0x30fc }, + { 0x04b1, 0x30a2 }, + { 0x04b2, 0x30a4 }, + { 0x04b3, 0x30a6 }, + { 0x04b4, 0x30a8 }, + { 0x04b5, 0x30aa }, + { 0x04b6, 0x30ab }, + { 0x04b7, 0x30ad }, + { 0x04b8, 0x30af }, + { 0x04b9, 0x30b1 }, + { 0x04ba, 0x30b3 }, + { 0x04bb, 0x30b5 }, + { 0x04bc, 0x30b7 }, + { 0x04bd, 0x30b9 }, + { 0x04be, 0x30bb }, + { 0x04bf, 0x30bd }, + { 0x04c0, 0x30bf }, + { 0x04c1, 0x30c1 }, + { 0x04c2, 0x30c4 }, + { 0x04c3, 0x30c6 }, + { 0x04c4, 0x30c8 }, + { 0x04c5, 0x30ca }, + { 0x04c6, 0x30cb }, + { 0x04c7, 0x30cc }, + { 0x04c8, 0x30cd }, + { 0x04c9, 0x30ce }, + { 0x04ca, 0x30cf }, + { 0x04cb, 0x30d2 }, + { 0x04cc, 0x30d5 }, + { 0x04cd, 0x30d8 }, + { 0x04ce, 0x30db }, + { 0x04cf, 0x30de }, + { 0x04d0, 0x30df }, + { 0x04d1, 0x30e0 }, + { 0x04d2, 0x30e1 }, + { 0x04d3, 0x30e2 }, + { 0x04d4, 0x30e4 }, + { 0x04d5, 0x30e6 }, + { 0x04d6, 0x30e8 }, + { 0x04d7, 0x30e9 }, + { 0x04d8, 0x30ea }, + { 0x04d9, 0x30eb }, + { 0x04da, 0x30ec }, + { 0x04db, 0x30ed }, + { 0x04dc, 0x30ef }, + { 0x04dd, 0x30f3 }, + { 0x04de, 0x309b }, + { 0x04df, 0x309c }, + { 0x05ac, 0x060c }, + { 0x05bb, 0x061b }, + { 0x05bf, 0x061f }, + { 0x05c1, 0x0621 }, + { 0x05c2, 0x0622 }, + { 0x05c3, 0x0623 }, + { 0x05c4, 0x0624 }, + { 0x05c5, 0x0625 }, + { 0x05c6, 0x0626 }, + { 0x05c7, 0x0627 }, + { 0x05c8, 0x0628 }, + { 0x05c9, 0x0629 }, + { 0x05ca, 0x062a }, + { 0x05cb, 0x062b }, + { 0x05cc, 0x062c }, + { 0x05cd, 0x062d }, + { 0x05ce, 0x062e }, + { 0x05cf, 0x062f }, + { 0x05d0, 0x0630 }, + { 0x05d1, 0x0631 }, + { 0x05d2, 0x0632 }, + { 0x05d3, 0x0633 }, + { 0x05d4, 0x0634 }, + { 0x05d5, 0x0635 }, + { 0x05d6, 0x0636 }, + { 0x05d7, 0x0637 }, + { 0x05d8, 0x0638 }, + { 0x05d9, 0x0639 }, + { 0x05da, 0x063a }, + { 0x05e0, 0x0640 }, + { 0x05e1, 0x0641 }, + { 0x05e2, 0x0642 }, + { 0x05e3, 0x0643 }, + { 0x05e4, 0x0644 }, + { 0x05e5, 0x0645 }, + { 0x05e6, 0x0646 }, + { 0x05e7, 0x0647 }, + { 0x05e8, 0x0648 }, + { 0x05e9, 0x0649 }, + { 0x05ea, 0x064a }, + { 0x05eb, 0x064b }, + { 0x05ec, 0x064c }, + { 0x05ed, 0x064d }, + { 0x05ee, 0x064e }, + { 0x05ef, 0x064f }, + { 0x05f0, 0x0650 }, + { 0x05f1, 0x0651 }, + { 0x05f2, 0x0652 }, + { 0x06a1, 0x0452 }, + { 0x06a2, 0x0453 }, + { 0x06a3, 0x0451 }, + { 0x06a4, 0x0454 }, + { 0x06a5, 0x0455 }, + { 0x06a6, 0x0456 }, + { 0x06a7, 0x0457 }, + { 0x06a8, 0x0458 }, + { 0x06a9, 0x0459 }, + { 0x06aa, 0x045a }, + { 0x06ab, 0x045b }, + { 0x06ac, 0x045c }, + { 0x06ae, 0x045e }, + { 0x06af, 0x045f }, + { 0x06b0, 0x2116 }, + { 0x06b1, 0x0402 }, + { 0x06b2, 0x0403 }, + { 0x06b3, 0x0401 }, + { 0x06b4, 0x0404 }, + { 0x06b5, 0x0405 }, + { 0x06b6, 0x0406 }, + { 0x06b7, 0x0407 }, + { 0x06b8, 0x0408 }, + { 0x06b9, 0x0409 }, + { 0x06ba, 0x040a }, + { 0x06bb, 0x040b }, + { 0x06bc, 0x040c }, + { 0x06be, 0x040e }, + { 0x06bf, 0x040f }, + { 0x06c0, 0x044e }, + { 0x06c1, 0x0430 }, + { 0x06c2, 0x0431 }, + { 0x06c3, 0x0446 }, + { 0x06c4, 0x0434 }, + { 0x06c5, 0x0435 }, + { 0x06c6, 0x0444 }, + { 0x06c7, 0x0433 }, + { 0x06c8, 0x0445 }, + { 0x06c9, 0x0438 }, + { 0x06ca, 0x0439 }, + { 0x06cb, 0x043a }, + { 0x06cc, 0x043b }, + { 0x06cd, 0x043c }, + { 0x06ce, 0x043d }, + { 0x06cf, 0x043e }, + { 0x06d0, 0x043f }, + { 0x06d1, 0x044f }, + { 0x06d2, 0x0440 }, + { 0x06d3, 0x0441 }, + { 0x06d4, 0x0442 }, + { 0x06d5, 0x0443 }, + { 0x06d6, 0x0436 }, + { 0x06d7, 0x0432 }, + { 0x06d8, 0x044c }, + { 0x06d9, 0x044b }, + { 0x06da, 0x0437 }, + { 0x06db, 0x0448 }, + { 0x06dc, 0x044d }, + { 0x06dd, 0x0449 }, + { 0x06de, 0x0447 }, + { 0x06df, 0x044a }, + { 0x06e0, 0x042e }, + { 0x06e1, 0x0410 }, + { 0x06e2, 0x0411 }, + { 0x06e3, 0x0426 }, + { 0x06e4, 0x0414 }, + { 0x06e5, 0x0415 }, + { 0x06e6, 0x0424 }, + { 0x06e7, 0x0413 }, + { 0x06e8, 0x0425 }, + { 0x06e9, 0x0418 }, + { 0x06ea, 0x0419 }, + { 0x06eb, 0x041a }, + { 0x06ec, 0x041b }, + { 0x06ed, 0x041c }, + { 0x06ee, 0x041d }, + { 0x06ef, 0x041e }, + { 0x06f0, 0x041f }, + { 0x06f1, 0x042f }, + { 0x06f2, 0x0420 }, + { 0x06f3, 0x0421 }, + { 0x06f4, 0x0422 }, + { 0x06f5, 0x0423 }, + { 0x06f6, 0x0416 }, + { 0x06f7, 0x0412 }, + { 0x06f8, 0x042c }, + { 0x06f9, 0x042b }, + { 0x06fa, 0x0417 }, + { 0x06fb, 0x0428 }, + { 0x06fc, 0x042d }, + { 0x06fd, 0x0429 }, + { 0x06fe, 0x0427 }, + { 0x06ff, 0x042a }, + { 0x07a1, 0x0386 }, + { 0x07a2, 0x0388 }, + { 0x07a3, 0x0389 }, + { 0x07a4, 0x038a }, + { 0x07a5, 0x03aa }, + { 0x07a7, 0x038c }, + { 0x07a8, 0x038e }, + { 0x07a9, 0x03ab }, + { 0x07ab, 0x038f }, + { 0x07ae, 0x0385 }, + { 0x07af, 0x2015 }, + { 0x07b1, 0x03ac }, + { 0x07b2, 0x03ad }, + { 0x07b3, 0x03ae }, + { 0x07b4, 0x03af }, + { 0x07b5, 0x03ca }, + { 0x07b6, 0x0390 }, + { 0x07b7, 0x03cc }, + { 0x07b8, 0x03cd }, + { 0x07b9, 0x03cb }, + { 0x07ba, 0x03b0 }, + { 0x07bb, 0x03ce }, + { 0x07c1, 0x0391 }, + { 0x07c2, 0x0392 }, + { 0x07c3, 0x0393 }, + { 0x07c4, 0x0394 }, + { 0x07c5, 0x0395 }, + { 0x07c6, 0x0396 }, + { 0x07c7, 0x0397 }, + { 0x07c8, 0x0398 }, + { 0x07c9, 0x0399 }, + { 0x07ca, 0x039a }, + { 0x07cb, 0x039b }, + { 0x07cc, 0x039c }, + { 0x07cd, 0x039d }, + { 0x07ce, 0x039e }, + { 0x07cf, 0x039f }, + { 0x07d0, 0x03a0 }, + { 0x07d1, 0x03a1 }, + { 0x07d2, 0x03a3 }, + { 0x07d4, 0x03a4 }, + { 0x07d5, 0x03a5 }, + { 0x07d6, 0x03a6 }, + { 0x07d7, 0x03a7 }, + { 0x07d8, 0x03a8 }, + { 0x07d9, 0x03a9 }, + { 0x07e1, 0x03b1 }, + { 0x07e2, 0x03b2 }, + { 0x07e3, 0x03b3 }, + { 0x07e4, 0x03b4 }, + { 0x07e5, 0x03b5 }, + { 0x07e6, 0x03b6 }, + { 0x07e7, 0x03b7 }, + { 0x07e8, 0x03b8 }, + { 0x07e9, 0x03b9 }, + { 0x07ea, 0x03ba }, + { 0x07eb, 0x03bb }, + { 0x07ec, 0x03bc }, + { 0x07ed, 0x03bd }, + { 0x07ee, 0x03be }, + { 0x07ef, 0x03bf }, + { 0x07f0, 0x03c0 }, + { 0x07f1, 0x03c1 }, + { 0x07f2, 0x03c3 }, + { 0x07f3, 0x03c2 }, + { 0x07f4, 0x03c4 }, + { 0x07f5, 0x03c5 }, + { 0x07f6, 0x03c6 }, + { 0x07f7, 0x03c7 }, + { 0x07f8, 0x03c8 }, + { 0x07f9, 0x03c9 }, + { 0x08a1, 0x23b7 }, + { 0x08a2, 0x250c }, + { 0x08a3, 0x2500 }, + { 0x08a4, 0x2320 }, + { 0x08a5, 0x2321 }, + { 0x08a6, 0x2502 }, + { 0x08a7, 0x23a1 }, + { 0x08a8, 0x23a3 }, + { 0x08a9, 0x23a4 }, + { 0x08aa, 0x23a6 }, + { 0x08ab, 0x239b }, + { 0x08ac, 0x239d }, + { 0x08ad, 0x239e }, + { 0x08ae, 0x23a0 }, + { 0x08af, 0x23a8 }, + { 0x08b0, 0x23ac }, + { 0x08bc, 0x2264 }, + { 0x08bd, 0x2260 }, + { 0x08be, 0x2265 }, + { 0x08bf, 0x222b }, + { 0x08c0, 0x2234 }, + { 0x08c1, 0x221d }, + { 0x08c2, 0x221e }, + { 0x08c5, 0x2207 }, + { 0x08c8, 0x223c }, + { 0x08c9, 0x2243 }, + { 0x08cd, 0x21d4 }, + { 0x08ce, 0x21d2 }, + { 0x08cf, 0x2261 }, + { 0x08d6, 0x221a }, + { 0x08da, 0x2282 }, + { 0x08db, 0x2283 }, + { 0x08dc, 0x2229 }, + { 0x08dd, 0x222a }, + { 0x08de, 0x2227 }, + { 0x08df, 0x2228 }, + { 0x08ef, 0x2202 }, + { 0x08f6, 0x0192 }, + { 0x08fb, 0x2190 }, + { 0x08fc, 0x2191 }, + { 0x08fd, 0x2192 }, + { 0x08fe, 0x2193 }, + { 0x09e0, 0x25c6 }, + { 0x09e1, 0x2592 }, + { 0x09e2, 0x2409 }, + { 0x09e3, 0x240c }, + { 0x09e4, 0x240d }, + { 0x09e5, 0x240a }, + { 0x09e8, 0x2424 }, + { 0x09e9, 0x240b }, + { 0x09ea, 0x2518 }, + { 0x09eb, 0x2510 }, + { 0x09ec, 0x250c }, + { 0x09ed, 0x2514 }, + { 0x09ee, 0x253c }, + { 0x09ef, 0x23ba }, + { 0x09f0, 0x23bb }, + { 0x09f1, 0x2500 }, + { 0x09f2, 0x23bc }, + { 0x09f3, 0x23bd }, + { 0x09f4, 0x251c }, + { 0x09f5, 0x2524 }, + { 0x09f6, 0x2534 }, + { 0x09f7, 0x252c }, + { 0x09f8, 0x2502 }, + { 0x0aa1, 0x2003 }, + { 0x0aa2, 0x2002 }, + { 0x0aa3, 0x2004 }, + { 0x0aa4, 0x2005 }, + { 0x0aa5, 0x2007 }, + { 0x0aa6, 0x2008 }, + { 0x0aa7, 0x2009 }, + { 0x0aa8, 0x200a }, + { 0x0aa9, 0x2014 }, + { 0x0aaa, 0x2013 }, + { 0x0aae, 0x2026 }, + { 0x0aaf, 0x2025 }, + { 0x0ab0, 0x2153 }, + { 0x0ab1, 0x2154 }, + { 0x0ab2, 0x2155 }, + { 0x0ab3, 0x2156 }, + { 0x0ab4, 0x2157 }, + { 0x0ab5, 0x2158 }, + { 0x0ab6, 0x2159 }, + { 0x0ab7, 0x215a }, + { 0x0ab8, 0x2105 }, + { 0x0abb, 0x2012 }, + { 0x0abc, 0x2329 }, + { 0x0abe, 0x232a }, + { 0x0ac3, 0x215b }, + { 0x0ac4, 0x215c }, + { 0x0ac5, 0x215d }, + { 0x0ac6, 0x215e }, + { 0x0ac9, 0x2122 }, + { 0x0aca, 0x2613 }, + { 0x0acc, 0x25c1 }, + { 0x0acd, 0x25b7 }, + { 0x0ace, 0x25cb }, + { 0x0acf, 0x25af }, + { 0x0ad0, 0x2018 }, + { 0x0ad1, 0x2019 }, + { 0x0ad2, 0x201c }, + { 0x0ad3, 0x201d }, + { 0x0ad4, 0x211e }, + { 0x0ad6, 0x2032 }, + { 0x0ad7, 0x2033 }, + { 0x0ad9, 0x271d }, + { 0x0adb, 0x25ac }, + { 0x0adc, 0x25c0 }, + { 0x0add, 0x25b6 }, + { 0x0ade, 0x25cf }, + { 0x0adf, 0x25ae }, + { 0x0ae0, 0x25e6 }, + { 0x0ae1, 0x25ab }, + { 0x0ae2, 0x25ad }, + { 0x0ae3, 0x25b3 }, + { 0x0ae4, 0x25bd }, + { 0x0ae5, 0x2606 }, + { 0x0ae6, 0x2022 }, + { 0x0ae7, 0x25aa }, + { 0x0ae8, 0x25b2 }, + { 0x0ae9, 0x25bc }, + { 0x0aea, 0x261c }, + { 0x0aeb, 0x261e }, + { 0x0aec, 0x2663 }, + { 0x0aed, 0x2666 }, + { 0x0aee, 0x2665 }, + { 0x0af0, 0x2720 }, + { 0x0af1, 0x2020 }, + { 0x0af2, 0x2021 }, + { 0x0af3, 0x2713 }, + { 0x0af4, 0x2717 }, + { 0x0af5, 0x266f }, + { 0x0af6, 0x266d }, + { 0x0af7, 0x2642 }, + { 0x0af8, 0x2640 }, + { 0x0af9, 0x260e }, + { 0x0afa, 0x2315 }, + { 0x0afb, 0x2117 }, + { 0x0afc, 0x2038 }, + { 0x0afd, 0x201a }, + { 0x0afe, 0x201e }, + { 0x0ba3, 0x003c }, + { 0x0ba6, 0x003e }, + { 0x0ba8, 0x2228 }, + { 0x0ba9, 0x2227 }, + { 0x0bc0, 0x00af }, + { 0x0bc2, 0x22a5 }, + { 0x0bc3, 0x2229 }, + { 0x0bc4, 0x230a }, + { 0x0bc6, 0x005f }, + { 0x0bca, 0x2218 }, + { 0x0bcc, 0x2395 }, + { 0x0bce, 0x22a4 }, + { 0x0bcf, 0x25cb }, + { 0x0bd3, 0x2308 }, + { 0x0bd6, 0x222a }, + { 0x0bd8, 0x2283 }, + { 0x0bda, 0x2282 }, + { 0x0bdc, 0x22a2 }, + { 0x0bfc, 0x22a3 }, + { 0x0cdf, 0x2017 }, + { 0x0ce0, 0x05d0 }, + { 0x0ce1, 0x05d1 }, + { 0x0ce2, 0x05d2 }, + { 0x0ce3, 0x05d3 }, + { 0x0ce4, 0x05d4 }, + { 0x0ce5, 0x05d5 }, + { 0x0ce6, 0x05d6 }, + { 0x0ce7, 0x05d7 }, + { 0x0ce8, 0x05d8 }, + { 0x0ce9, 0x05d9 }, + { 0x0cea, 0x05da }, + { 0x0ceb, 0x05db }, + { 0x0cec, 0x05dc }, + { 0x0ced, 0x05dd }, + { 0x0cee, 0x05de }, + { 0x0cef, 0x05df }, + { 0x0cf0, 0x05e0 }, + { 0x0cf1, 0x05e1 }, + { 0x0cf2, 0x05e2 }, + { 0x0cf3, 0x05e3 }, + { 0x0cf4, 0x05e4 }, + { 0x0cf5, 0x05e5 }, + { 0x0cf6, 0x05e6 }, + { 0x0cf7, 0x05e7 }, + { 0x0cf8, 0x05e8 }, + { 0x0cf9, 0x05e9 }, + { 0x0cfa, 0x05ea }, + { 0x0da1, 0x0e01 }, + { 0x0da2, 0x0e02 }, + { 0x0da3, 0x0e03 }, + { 0x0da4, 0x0e04 }, + { 0x0da5, 0x0e05 }, + { 0x0da6, 0x0e06 }, + { 0x0da7, 0x0e07 }, + { 0x0da8, 0x0e08 }, + { 0x0da9, 0x0e09 }, + { 0x0daa, 0x0e0a }, + { 0x0dab, 0x0e0b }, + { 0x0dac, 0x0e0c }, + { 0x0dad, 0x0e0d }, + { 0x0dae, 0x0e0e }, + { 0x0daf, 0x0e0f }, + { 0x0db0, 0x0e10 }, + { 0x0db1, 0x0e11 }, + { 0x0db2, 0x0e12 }, + { 0x0db3, 0x0e13 }, + { 0x0db4, 0x0e14 }, + { 0x0db5, 0x0e15 }, + { 0x0db6, 0x0e16 }, + { 0x0db7, 0x0e17 }, + { 0x0db8, 0x0e18 }, + { 0x0db9, 0x0e19 }, + { 0x0dba, 0x0e1a }, + { 0x0dbb, 0x0e1b }, + { 0x0dbc, 0x0e1c }, + { 0x0dbd, 0x0e1d }, + { 0x0dbe, 0x0e1e }, + { 0x0dbf, 0x0e1f }, + { 0x0dc0, 0x0e20 }, + { 0x0dc1, 0x0e21 }, + { 0x0dc2, 0x0e22 }, + { 0x0dc3, 0x0e23 }, + { 0x0dc4, 0x0e24 }, + { 0x0dc5, 0x0e25 }, + { 0x0dc6, 0x0e26 }, + { 0x0dc7, 0x0e27 }, + { 0x0dc8, 0x0e28 }, + { 0x0dc9, 0x0e29 }, + { 0x0dca, 0x0e2a }, + { 0x0dcb, 0x0e2b }, + { 0x0dcc, 0x0e2c }, + { 0x0dcd, 0x0e2d }, + { 0x0dce, 0x0e2e }, + { 0x0dcf, 0x0e2f }, + { 0x0dd0, 0x0e30 }, + { 0x0dd1, 0x0e31 }, + { 0x0dd2, 0x0e32 }, + { 0x0dd3, 0x0e33 }, + { 0x0dd4, 0x0e34 }, + { 0x0dd5, 0x0e35 }, + { 0x0dd6, 0x0e36 }, + { 0x0dd7, 0x0e37 }, + { 0x0dd8, 0x0e38 }, + { 0x0dd9, 0x0e39 }, + { 0x0dda, 0x0e3a }, + { 0x0ddf, 0x0e3f }, + { 0x0de0, 0x0e40 }, + { 0x0de1, 0x0e41 }, + { 0x0de2, 0x0e42 }, + { 0x0de3, 0x0e43 }, + { 0x0de4, 0x0e44 }, + { 0x0de5, 0x0e45 }, + { 0x0de6, 0x0e46 }, + { 0x0de7, 0x0e47 }, + { 0x0de8, 0x0e48 }, + { 0x0de9, 0x0e49 }, + { 0x0dea, 0x0e4a }, + { 0x0deb, 0x0e4b }, + { 0x0dec, 0x0e4c }, + { 0x0ded, 0x0e4d }, + { 0x0df0, 0x0e50 }, + { 0x0df1, 0x0e51 }, + { 0x0df2, 0x0e52 }, + { 0x0df3, 0x0e53 }, + { 0x0df4, 0x0e54 }, + { 0x0df5, 0x0e55 }, + { 0x0df6, 0x0e56 }, + { 0x0df7, 0x0e57 }, + { 0x0df8, 0x0e58 }, + { 0x0df9, 0x0e59 }, + { 0x0ea1, 0x3131 }, + { 0x0ea2, 0x3132 }, + { 0x0ea3, 0x3133 }, + { 0x0ea4, 0x3134 }, + { 0x0ea5, 0x3135 }, + { 0x0ea6, 0x3136 }, + { 0x0ea7, 0x3137 }, + { 0x0ea8, 0x3138 }, + { 0x0ea9, 0x3139 }, + { 0x0eaa, 0x313a }, + { 0x0eab, 0x313b }, + { 0x0eac, 0x313c }, + { 0x0ead, 0x313d }, + { 0x0eae, 0x313e }, + { 0x0eaf, 0x313f }, + { 0x0eb0, 0x3140 }, + { 0x0eb1, 0x3141 }, + { 0x0eb2, 0x3142 }, + { 0x0eb3, 0x3143 }, + { 0x0eb4, 0x3144 }, + { 0x0eb5, 0x3145 }, + { 0x0eb6, 0x3146 }, + { 0x0eb7, 0x3147 }, + { 0x0eb8, 0x3148 }, + { 0x0eb9, 0x3149 }, + { 0x0eba, 0x314a }, + { 0x0ebb, 0x314b }, + { 0x0ebc, 0x314c }, + { 0x0ebd, 0x314d }, + { 0x0ebe, 0x314e }, + { 0x0ebf, 0x314f }, + { 0x0ec0, 0x3150 }, + { 0x0ec1, 0x3151 }, + { 0x0ec2, 0x3152 }, + { 0x0ec3, 0x3153 }, + { 0x0ec4, 0x3154 }, + { 0x0ec5, 0x3155 }, + { 0x0ec6, 0x3156 }, + { 0x0ec7, 0x3157 }, + { 0x0ec8, 0x3158 }, + { 0x0ec9, 0x3159 }, + { 0x0eca, 0x315a }, + { 0x0ecb, 0x315b }, + { 0x0ecc, 0x315c }, + { 0x0ecd, 0x315d }, + { 0x0ece, 0x315e }, + { 0x0ecf, 0x315f }, + { 0x0ed0, 0x3160 }, + { 0x0ed1, 0x3161 }, + { 0x0ed2, 0x3162 }, + { 0x0ed3, 0x3163 }, + { 0x0ed4, 0x11a8 }, + { 0x0ed5, 0x11a9 }, + { 0x0ed6, 0x11aa }, + { 0x0ed7, 0x11ab }, + { 0x0ed8, 0x11ac }, + { 0x0ed9, 0x11ad }, + { 0x0eda, 0x11ae }, + { 0x0edb, 0x11af }, + { 0x0edc, 0x11b0 }, + { 0x0edd, 0x11b1 }, + { 0x0ede, 0x11b2 }, + { 0x0edf, 0x11b3 }, + { 0x0ee0, 0x11b4 }, + { 0x0ee1, 0x11b5 }, + { 0x0ee2, 0x11b6 }, + { 0x0ee3, 0x11b7 }, + { 0x0ee4, 0x11b8 }, + { 0x0ee5, 0x11b9 }, + { 0x0ee6, 0x11ba }, + { 0x0ee7, 0x11bb }, + { 0x0ee8, 0x11bc }, + { 0x0ee9, 0x11bd }, + { 0x0eea, 0x11be }, + { 0x0eeb, 0x11bf }, + { 0x0eec, 0x11c0 }, + { 0x0eed, 0x11c1 }, + { 0x0eee, 0x11c2 }, + { 0x0eef, 0x316d }, + { 0x0ef0, 0x3171 }, + { 0x0ef1, 0x3178 }, + { 0x0ef2, 0x317f }, + { 0x0ef3, 0x3181 }, + { 0x0ef4, 0x3184 }, + { 0x0ef5, 0x3186 }, + { 0x0ef6, 0x318d }, + { 0x0ef7, 0x318e }, + { 0x0ef8, 0x11eb }, + { 0x0ef9, 0x11f0 }, + { 0x0efa, 0x11f9 }, + { 0x0eff, 0x20a9 }, + { 0x13a4, 0x20ac }, + { 0x13bc, 0x0152 }, + { 0x13bd, 0x0153 }, + { 0x13be, 0x0178 }, + { 0x20ac, 0x20ac }, + { 0xfe50, '`' }, + { 0xfe51, 0x00b4 }, + { 0xfe52, '^' }, + { 0xfe53, '~' }, + { 0xfe54, 0x00af }, + { 0xfe55, 0x02d8 }, + { 0xfe56, 0x02d9 }, + { 0xfe57, 0x00a8 }, + { 0xfe58, 0x02da }, + { 0xfe59, 0x02dd }, + { 0xfe5a, 0x02c7 }, + { 0xfe5b, 0x00b8 }, + { 0xfe5c, 0x02db }, + { 0xfe5d, 0x037a }, + { 0xfe5e, 0x309b }, + { 0xfe5f, 0x309c }, + { 0xfe63, '/' }, + { 0xfe64, 0x02bc }, + { 0xfe65, 0x02bd }, + { 0xfe66, 0x02f5 }, + { 0xfe67, 0x02f3 }, + { 0xfe68, 0x02cd }, + { 0xfe69, 0xa788 }, + { 0xfe6a, 0x02f7 }, + { 0xfe6e, ',' }, + { 0xfe6f, 0x00a4 }, + { 0xfe80, 'a' }, /* XK_dead_a */ + { 0xfe81, 'A' }, /* XK_dead_A */ + { 0xfe82, 'e' }, /* XK_dead_e */ + { 0xfe83, 'E' }, /* XK_dead_E */ + { 0xfe84, 'i' }, /* XK_dead_i */ + { 0xfe85, 'I' }, /* XK_dead_I */ + { 0xfe86, 'o' }, /* XK_dead_o */ + { 0xfe87, 'O' }, /* XK_dead_O */ + { 0xfe88, 'u' }, /* XK_dead_u */ + { 0xfe89, 'U' }, /* XK_dead_U */ + { 0xfe8a, 0x0259 }, + { 0xfe8b, 0x018f }, + { 0xfe8c, 0x00b5 }, + { 0xfe90, '_' }, + { 0xfe91, 0x02c8 }, + { 0xfe92, 0x02cc }, + { 0xff80 /*XKB_KEY_KP_Space*/, ' ' }, + { 0xff95 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xff96 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xff97 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xff98 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xff99 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xff9a /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xff9b /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xff9c /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xff9d /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xff9e /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffaa /*XKB_KEY_KP_Multiply*/, '*' }, + { 0xffab /*XKB_KEY_KP_Add*/, '+' }, + { 0xffac /*XKB_KEY_KP_Separator*/, ',' }, + { 0xffad /*XKB_KEY_KP_Subtract*/, '-' }, + { 0xffae /*XKB_KEY_KP_Decimal*/, '.' }, + { 0xffaf /*XKB_KEY_KP_Divide*/, '/' }, + { 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 }, + { 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 }, + { 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 }, + { 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 }, + { 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 }, + { 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 }, + { 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 }, + { 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 }, + { 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 }, + { 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 }, + { 0xffbd /*XKB_KEY_KP_Equal*/, '=' } +}; + +_SOKOL_PRIVATE int _sapp_x11_error_handler(Display* display, XErrorEvent* event) { + _SOKOL_UNUSED(display); + _sapp.x11.error_code = event->error_code; + return 0; +} + +_SOKOL_PRIVATE void _sapp_x11_grab_error_handler(void) { + _sapp.x11.error_code = Success; + XSetErrorHandler(_sapp_x11_error_handler); +} + +_SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { + XSync(_sapp.x11.display, False); + XSetErrorHandler(NULL); +} + +_SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { + _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); + _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); + _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); + _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); + _sapp.x11.NET_WM_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_NAME", False); + _sapp.x11.NET_WM_ICON_NAME = XInternAtom(_sapp.x11.display, "_NET_WM_ICON_NAME", False); + _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); + _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); + _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); + _sapp.x11.CLIPBOARD = XInternAtom(_sapp.x11.display, "CLIPBOARD", False); + _sapp.x11.TARGETS = XInternAtom(_sapp.x11.display, "TARGETS", False); + if (_sapp.drop.enabled) { + _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); + _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); + _sapp.x11.xdnd.XdndPosition = XInternAtom(_sapp.x11.display, "XdndPosition", False); + _sapp.x11.xdnd.XdndStatus = XInternAtom(_sapp.x11.display, "XdndStatus", False); + _sapp.x11.xdnd.XdndActionCopy = XInternAtom(_sapp.x11.display, "XdndActionCopy", False); + _sapp.x11.xdnd.XdndDrop = XInternAtom(_sapp.x11.display, "XdndDrop", False); + _sapp.x11.xdnd.XdndFinished = XInternAtom(_sapp.x11.display, "XdndFinished", False); + _sapp.x11.xdnd.XdndSelection = XInternAtom(_sapp.x11.display, "XdndSelection", False); + _sapp.x11.xdnd.XdndTypeList = XInternAtom(_sapp.x11.display, "XdndTypeList", False); + _sapp.x11.xdnd.text_uri_list = XInternAtom(_sapp.x11.display, "text/uri-list", False); + } + + /* check Xi extension for raw mouse input */ + if (XQueryExtension(_sapp.x11.display, "XInputExtension", &_sapp.x11.xi.major_opcode, &_sapp.x11.xi.event_base, &_sapp.x11.xi.error_base)) { + _sapp.x11.xi.major = 2; + _sapp.x11.xi.minor = 0; + if (XIQueryVersion(_sapp.x11.display, &_sapp.x11.xi.major, &_sapp.x11.xi.minor) == Success) { + _sapp.x11.xi.available = true; + } + } +} + +// translate the X11 KeySyms for a key to sokol-app key code +// NOTE: this is only used as a fallback, in case the XBK method fails +// it is layout-dependent and will fail partially on most non-US layouts. +// +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_keysyms(const KeySym* keysyms, int width) { + if (width > 1) { + switch (keysyms[1]) { + case XK_KP_0: return SAPP_KEYCODE_KP_0; + case XK_KP_1: return SAPP_KEYCODE_KP_1; + case XK_KP_2: return SAPP_KEYCODE_KP_2; + case XK_KP_3: return SAPP_KEYCODE_KP_3; + case XK_KP_4: return SAPP_KEYCODE_KP_4; + case XK_KP_5: return SAPP_KEYCODE_KP_5; + case XK_KP_6: return SAPP_KEYCODE_KP_6; + case XK_KP_7: return SAPP_KEYCODE_KP_7; + case XK_KP_8: return SAPP_KEYCODE_KP_8; + case XK_KP_9: return SAPP_KEYCODE_KP_9; + case XK_KP_Separator: + case XK_KP_Decimal: return SAPP_KEYCODE_KP_DECIMAL; + case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; + case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; + default: break; + } + } + + switch (keysyms[0]) { + case XK_Escape: return SAPP_KEYCODE_ESCAPE; + case XK_Tab: return SAPP_KEYCODE_TAB; + case XK_Shift_L: return SAPP_KEYCODE_LEFT_SHIFT; + case XK_Shift_R: return SAPP_KEYCODE_RIGHT_SHIFT; + case XK_Control_L: return SAPP_KEYCODE_LEFT_CONTROL; + case XK_Control_R: return SAPP_KEYCODE_RIGHT_CONTROL; + case XK_Meta_L: + case XK_Alt_L: return SAPP_KEYCODE_LEFT_ALT; + case XK_Mode_switch: // Mapped to Alt_R on many keyboards + case XK_ISO_Level3_Shift: // AltGr on at least some machines + case XK_Meta_R: + case XK_Alt_R: return SAPP_KEYCODE_RIGHT_ALT; + case XK_Super_L: return SAPP_KEYCODE_LEFT_SUPER; + case XK_Super_R: return SAPP_KEYCODE_RIGHT_SUPER; + case XK_Menu: return SAPP_KEYCODE_MENU; + case XK_Num_Lock: return SAPP_KEYCODE_NUM_LOCK; + case XK_Caps_Lock: return SAPP_KEYCODE_CAPS_LOCK; + case XK_Print: return SAPP_KEYCODE_PRINT_SCREEN; + case XK_Scroll_Lock: return SAPP_KEYCODE_SCROLL_LOCK; + case XK_Pause: return SAPP_KEYCODE_PAUSE; + case XK_Delete: return SAPP_KEYCODE_DELETE; + case XK_BackSpace: return SAPP_KEYCODE_BACKSPACE; + case XK_Return: return SAPP_KEYCODE_ENTER; + case XK_Home: return SAPP_KEYCODE_HOME; + case XK_End: return SAPP_KEYCODE_END; + case XK_Page_Up: return SAPP_KEYCODE_PAGE_UP; + case XK_Page_Down: return SAPP_KEYCODE_PAGE_DOWN; + case XK_Insert: return SAPP_KEYCODE_INSERT; + case XK_Left: return SAPP_KEYCODE_LEFT; + case XK_Right: return SAPP_KEYCODE_RIGHT; + case XK_Down: return SAPP_KEYCODE_DOWN; + case XK_Up: return SAPP_KEYCODE_UP; + case XK_F1: return SAPP_KEYCODE_F1; + case XK_F2: return SAPP_KEYCODE_F2; + case XK_F3: return SAPP_KEYCODE_F3; + case XK_F4: return SAPP_KEYCODE_F4; + case XK_F5: return SAPP_KEYCODE_F5; + case XK_F6: return SAPP_KEYCODE_F6; + case XK_F7: return SAPP_KEYCODE_F7; + case XK_F8: return SAPP_KEYCODE_F8; + case XK_F9: return SAPP_KEYCODE_F9; + case XK_F10: return SAPP_KEYCODE_F10; + case XK_F11: return SAPP_KEYCODE_F11; + case XK_F12: return SAPP_KEYCODE_F12; + case XK_F13: return SAPP_KEYCODE_F13; + case XK_F14: return SAPP_KEYCODE_F14; + case XK_F15: return SAPP_KEYCODE_F15; + case XK_F16: return SAPP_KEYCODE_F16; + case XK_F17: return SAPP_KEYCODE_F17; + case XK_F18: return SAPP_KEYCODE_F18; + case XK_F19: return SAPP_KEYCODE_F19; + case XK_F20: return SAPP_KEYCODE_F20; + case XK_F21: return SAPP_KEYCODE_F21; + case XK_F22: return SAPP_KEYCODE_F22; + case XK_F23: return SAPP_KEYCODE_F23; + case XK_F24: return SAPP_KEYCODE_F24; + case XK_F25: return SAPP_KEYCODE_F25; + + // numeric keypad + case XK_KP_Divide: return SAPP_KEYCODE_KP_DIVIDE; + case XK_KP_Multiply: return SAPP_KEYCODE_KP_MULTIPLY; + case XK_KP_Subtract: return SAPP_KEYCODE_KP_SUBTRACT; + case XK_KP_Add: return SAPP_KEYCODE_KP_ADD; + + // these should have been detected in secondary keysym test above! + case XK_KP_Insert: return SAPP_KEYCODE_KP_0; + case XK_KP_End: return SAPP_KEYCODE_KP_1; + case XK_KP_Down: return SAPP_KEYCODE_KP_2; + case XK_KP_Page_Down: return SAPP_KEYCODE_KP_3; + case XK_KP_Left: return SAPP_KEYCODE_KP_4; + case XK_KP_Right: return SAPP_KEYCODE_KP_6; + case XK_KP_Home: return SAPP_KEYCODE_KP_7; + case XK_KP_Up: return SAPP_KEYCODE_KP_8; + case XK_KP_Page_Up: return SAPP_KEYCODE_KP_9; + case XK_KP_Delete: return SAPP_KEYCODE_KP_DECIMAL; + case XK_KP_Equal: return SAPP_KEYCODE_KP_EQUAL; + case XK_KP_Enter: return SAPP_KEYCODE_KP_ENTER; + + // last resort: Check for printable keys (should not happen if the XKB + // extension is available). This will give a layout dependent mapping + // (which is wrong, and we may miss some keys, especially on non-US + // keyboards), but it's better than nothing... + case XK_a: return SAPP_KEYCODE_A; + case XK_b: return SAPP_KEYCODE_B; + case XK_c: return SAPP_KEYCODE_C; + case XK_d: return SAPP_KEYCODE_D; + case XK_e: return SAPP_KEYCODE_E; + case XK_f: return SAPP_KEYCODE_F; + case XK_g: return SAPP_KEYCODE_G; + case XK_h: return SAPP_KEYCODE_H; + case XK_i: return SAPP_KEYCODE_I; + case XK_j: return SAPP_KEYCODE_J; + case XK_k: return SAPP_KEYCODE_K; + case XK_l: return SAPP_KEYCODE_L; + case XK_m: return SAPP_KEYCODE_M; + case XK_n: return SAPP_KEYCODE_N; + case XK_o: return SAPP_KEYCODE_O; + case XK_p: return SAPP_KEYCODE_P; + case XK_q: return SAPP_KEYCODE_Q; + case XK_r: return SAPP_KEYCODE_R; + case XK_s: return SAPP_KEYCODE_S; + case XK_t: return SAPP_KEYCODE_T; + case XK_u: return SAPP_KEYCODE_U; + case XK_v: return SAPP_KEYCODE_V; + case XK_w: return SAPP_KEYCODE_W; + case XK_x: return SAPP_KEYCODE_X; + case XK_y: return SAPP_KEYCODE_Y; + case XK_z: return SAPP_KEYCODE_Z; + case XK_1: return SAPP_KEYCODE_1; + case XK_2: return SAPP_KEYCODE_2; + case XK_3: return SAPP_KEYCODE_3; + case XK_4: return SAPP_KEYCODE_4; + case XK_5: return SAPP_KEYCODE_5; + case XK_6: return SAPP_KEYCODE_6; + case XK_7: return SAPP_KEYCODE_7; + case XK_8: return SAPP_KEYCODE_8; + case XK_9: return SAPP_KEYCODE_9; + case XK_0: return SAPP_KEYCODE_0; + case XK_space: return SAPP_KEYCODE_SPACE; + case XK_minus: return SAPP_KEYCODE_MINUS; + case XK_equal: return SAPP_KEYCODE_EQUAL; + case XK_bracketleft: return SAPP_KEYCODE_LEFT_BRACKET; + case XK_bracketright: return SAPP_KEYCODE_RIGHT_BRACKET; + case XK_backslash: return SAPP_KEYCODE_BACKSLASH; + case XK_semicolon: return SAPP_KEYCODE_SEMICOLON; + case XK_apostrophe: return SAPP_KEYCODE_APOSTROPHE; + case XK_grave: return SAPP_KEYCODE_GRAVE_ACCENT; + case XK_comma: return SAPP_KEYCODE_COMMA; + case XK_period: return SAPP_KEYCODE_PERIOD; + case XK_slash: return SAPP_KEYCODE_SLASH; + case XK_less: return SAPP_KEYCODE_WORLD_1; // At least in some layouts... + default: break; + } + + // no matching translation was found + return SAPP_KEYCODE_INVALID; +} + + +// setup dynamic keycode/scancode mapping tables, this is required +// for getting layout-independent keycodes on X11. +// +// see GLFW x11_init.c/createKeyTables() +_SOKOL_PRIVATE void _sapp_x11_init_keytable(void) { + for (int i = 0; i < SAPP_MAX_KEYCODES; i++) { + _sapp.keycodes[i] = SAPP_KEYCODE_INVALID; + } + // use XKB to determine physical key locations independently of the current keyboard layout + XkbDescPtr desc = XkbGetMap(_sapp.x11.display, 0, XkbUseCoreKbd); + SOKOL_ASSERT(desc); + XkbGetNames(_sapp.x11.display, XkbKeyNamesMask | XkbKeyAliasesMask, desc); + + const int scancode_min = desc->min_key_code; + const int scancode_max = desc->max_key_code; + + const struct { sapp_keycode key; const char* name; } keymap[] = { + { SAPP_KEYCODE_GRAVE_ACCENT, "TLDE" }, + { SAPP_KEYCODE_1, "AE01" }, + { SAPP_KEYCODE_2, "AE02" }, + { SAPP_KEYCODE_3, "AE03" }, + { SAPP_KEYCODE_4, "AE04" }, + { SAPP_KEYCODE_5, "AE05" }, + { SAPP_KEYCODE_6, "AE06" }, + { SAPP_KEYCODE_7, "AE07" }, + { SAPP_KEYCODE_8, "AE08" }, + { SAPP_KEYCODE_9, "AE09" }, + { SAPP_KEYCODE_0, "AE10" }, + { SAPP_KEYCODE_MINUS, "AE11" }, + { SAPP_KEYCODE_EQUAL, "AE12" }, + { SAPP_KEYCODE_Q, "AD01" }, + { SAPP_KEYCODE_W, "AD02" }, + { SAPP_KEYCODE_E, "AD03" }, + { SAPP_KEYCODE_R, "AD04" }, + { SAPP_KEYCODE_T, "AD05" }, + { SAPP_KEYCODE_Y, "AD06" }, + { SAPP_KEYCODE_U, "AD07" }, + { SAPP_KEYCODE_I, "AD08" }, + { SAPP_KEYCODE_O, "AD09" }, + { SAPP_KEYCODE_P, "AD10" }, + { SAPP_KEYCODE_LEFT_BRACKET, "AD11" }, + { SAPP_KEYCODE_RIGHT_BRACKET, "AD12" }, + { SAPP_KEYCODE_A, "AC01" }, + { SAPP_KEYCODE_S, "AC02" }, + { SAPP_KEYCODE_D, "AC03" }, + { SAPP_KEYCODE_F, "AC04" }, + { SAPP_KEYCODE_G, "AC05" }, + { SAPP_KEYCODE_H, "AC06" }, + { SAPP_KEYCODE_J, "AC07" }, + { SAPP_KEYCODE_K, "AC08" }, + { SAPP_KEYCODE_L, "AC09" }, + { SAPP_KEYCODE_SEMICOLON, "AC10" }, + { SAPP_KEYCODE_APOSTROPHE, "AC11" }, + { SAPP_KEYCODE_Z, "AB01" }, + { SAPP_KEYCODE_X, "AB02" }, + { SAPP_KEYCODE_C, "AB03" }, + { SAPP_KEYCODE_V, "AB04" }, + { SAPP_KEYCODE_B, "AB05" }, + { SAPP_KEYCODE_N, "AB06" }, + { SAPP_KEYCODE_M, "AB07" }, + { SAPP_KEYCODE_COMMA, "AB08" }, + { SAPP_KEYCODE_PERIOD, "AB09" }, + { SAPP_KEYCODE_SLASH, "AB10" }, + { SAPP_KEYCODE_BACKSLASH, "BKSL" }, + { SAPP_KEYCODE_WORLD_1, "LSGT" }, + { SAPP_KEYCODE_SPACE, "SPCE" }, + { SAPP_KEYCODE_ESCAPE, "ESC" }, + { SAPP_KEYCODE_ENTER, "RTRN" }, + { SAPP_KEYCODE_TAB, "TAB" }, + { SAPP_KEYCODE_BACKSPACE, "BKSP" }, + { SAPP_KEYCODE_INSERT, "INS" }, + { SAPP_KEYCODE_DELETE, "DELE" }, + { SAPP_KEYCODE_RIGHT, "RGHT" }, + { SAPP_KEYCODE_LEFT, "LEFT" }, + { SAPP_KEYCODE_DOWN, "DOWN" }, + { SAPP_KEYCODE_UP, "UP" }, + { SAPP_KEYCODE_PAGE_UP, "PGUP" }, + { SAPP_KEYCODE_PAGE_DOWN, "PGDN" }, + { SAPP_KEYCODE_HOME, "HOME" }, + { SAPP_KEYCODE_END, "END" }, + { SAPP_KEYCODE_CAPS_LOCK, "CAPS" }, + { SAPP_KEYCODE_SCROLL_LOCK, "SCLK" }, + { SAPP_KEYCODE_NUM_LOCK, "NMLK" }, + { SAPP_KEYCODE_PRINT_SCREEN, "PRSC" }, + { SAPP_KEYCODE_PAUSE, "PAUS" }, + { SAPP_KEYCODE_F1, "FK01" }, + { SAPP_KEYCODE_F2, "FK02" }, + { SAPP_KEYCODE_F3, "FK03" }, + { SAPP_KEYCODE_F4, "FK04" }, + { SAPP_KEYCODE_F5, "FK05" }, + { SAPP_KEYCODE_F6, "FK06" }, + { SAPP_KEYCODE_F7, "FK07" }, + { SAPP_KEYCODE_F8, "FK08" }, + { SAPP_KEYCODE_F9, "FK09" }, + { SAPP_KEYCODE_F10, "FK10" }, + { SAPP_KEYCODE_F11, "FK11" }, + { SAPP_KEYCODE_F12, "FK12" }, + { SAPP_KEYCODE_F13, "FK13" }, + { SAPP_KEYCODE_F14, "FK14" }, + { SAPP_KEYCODE_F15, "FK15" }, + { SAPP_KEYCODE_F16, "FK16" }, + { SAPP_KEYCODE_F17, "FK17" }, + { SAPP_KEYCODE_F18, "FK18" }, + { SAPP_KEYCODE_F19, "FK19" }, + { SAPP_KEYCODE_F20, "FK20" }, + { SAPP_KEYCODE_F21, "FK21" }, + { SAPP_KEYCODE_F22, "FK22" }, + { SAPP_KEYCODE_F23, "FK23" }, + { SAPP_KEYCODE_F24, "FK24" }, + { SAPP_KEYCODE_F25, "FK25" }, + { SAPP_KEYCODE_KP_0, "KP0" }, + { SAPP_KEYCODE_KP_1, "KP1" }, + { SAPP_KEYCODE_KP_2, "KP2" }, + { SAPP_KEYCODE_KP_3, "KP3" }, + { SAPP_KEYCODE_KP_4, "KP4" }, + { SAPP_KEYCODE_KP_5, "KP5" }, + { SAPP_KEYCODE_KP_6, "KP6" }, + { SAPP_KEYCODE_KP_7, "KP7" }, + { SAPP_KEYCODE_KP_8, "KP8" }, + { SAPP_KEYCODE_KP_9, "KP9" }, + { SAPP_KEYCODE_KP_DECIMAL, "KPDL" }, + { SAPP_KEYCODE_KP_DIVIDE, "KPDV" }, + { SAPP_KEYCODE_KP_MULTIPLY, "KPMU" }, + { SAPP_KEYCODE_KP_SUBTRACT, "KPSU" }, + { SAPP_KEYCODE_KP_ADD, "KPAD" }, + { SAPP_KEYCODE_KP_ENTER, "KPEN" }, + { SAPP_KEYCODE_KP_EQUAL, "KPEQ" }, + { SAPP_KEYCODE_LEFT_SHIFT, "LFSH" }, + { SAPP_KEYCODE_LEFT_CONTROL, "LCTL" }, + { SAPP_KEYCODE_LEFT_ALT, "LALT" }, + { SAPP_KEYCODE_LEFT_SUPER, "LWIN" }, + { SAPP_KEYCODE_RIGHT_SHIFT, "RTSH" }, + { SAPP_KEYCODE_RIGHT_CONTROL, "RCTL" }, + { SAPP_KEYCODE_RIGHT_ALT, "RALT" }, + { SAPP_KEYCODE_RIGHT_ALT, "LVL3" }, + { SAPP_KEYCODE_RIGHT_ALT, "MDSW" }, + { SAPP_KEYCODE_RIGHT_SUPER, "RWIN" }, + { SAPP_KEYCODE_MENU, "MENU" } + }; + const int num_keymap_items = (int)(sizeof(keymap) / sizeof(keymap[0])); + + // find X11 keycode to sokol-app key code mapping + for (int scancode = scancode_min; scancode <= scancode_max; scancode++) { + sapp_keycode key = SAPP_KEYCODE_INVALID; + for (int i = 0; i < num_keymap_items; i++) { + if (strncmp(desc->names->keys[scancode].name, keymap[i].name, XkbKeyNameLength) == 0) { + key = keymap[i].key; + break; + } + } + + // fall back to key aliases in case the key name did not match + for (int i = 0; i < desc->names->num_key_aliases; i++) { + if (key != SAPP_KEYCODE_INVALID) { + break; + } + if (strncmp(desc->names->key_aliases[i].real, desc->names->keys[scancode].name, XkbKeyNameLength) != 0) { + continue; + } + for (int j = 0; j < num_keymap_items; j++) { + if (strncmp(desc->names->key_aliases[i].alias, keymap[i].name, XkbKeyNameLength) == 0) { + key = keymap[i].key; + break; + } + } + } + _sapp.keycodes[scancode] = key; + } + XkbFreeNames(desc, XkbKeyNamesMask, True); + XkbFreeKeyboard(desc, 0, True); + + int width = 0; + KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode_min, scancode_max - scancode_min + 1, &width); + for (int scancode = scancode_min; scancode <= scancode_max; scancode++) { + // translate untranslated key codes using the traditional X11 KeySym lookups + if (_sapp.keycodes[scancode] == SAPP_KEYCODE_INVALID) { + const size_t base = (size_t)((scancode - scancode_min) * width); + _sapp.keycodes[scancode] = _sapp_x11_translate_keysyms(&keysyms[base], width); + } + } + XFree(keysyms); +} + +_SOKOL_PRIVATE void _sapp_x11_query_system_dpi(void) { + /* from GLFW: + + NOTE: Default to the display-wide DPI as we don't currently have a policy + for which monitor a window is considered to be on + + _sapp.x11.dpi = DisplayWidth(_sapp.x11.display, _sapp.x11.screen) * + 25.4f / DisplayWidthMM(_sapp.x11.display, _sapp.x11.screen); + + NOTE: Basing the scale on Xft.dpi where available should provide the most + consistent user experience (matches Qt, Gtk, etc), although not + always the most accurate one + */ + bool dpi_ok = false; + char* rms = XResourceManagerString(_sapp.x11.display); + if (rms) { + XrmDatabase db = XrmGetStringDatabase(rms); + if (db) { + XrmValue value; + char* type = NULL; + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { + if (type && strcmp(type, "String") == 0) { + _sapp.x11.dpi = atof(value.addr); + dpi_ok = true; + } + } + XrmDestroyDatabase(db); + } + } + // fallback if querying DPI had failed: assume the standard DPI 96.0f + if (!dpi_ok) { + _sapp.x11.dpi = 96.0f; + _SAPP_WARN(LINUX_X11_QUERY_SYSTEM_DPI_FAILED); + } +} + +#if defined(_SAPP_GLX) + +_SOKOL_PRIVATE bool _sapp_glx_has_ext(const char* ext, const char* extensions) { + SOKOL_ASSERT(ext); + const char* start = extensions; + while (true) { + const char* where = strstr(start, ext); + if (!where) { + return false; + } + const char* terminator = where + strlen(ext); + if ((where == start) || (*(where - 1) == ' ')) { + if (*terminator == ' ' || *terminator == '\0') { + break; + } + } + start = terminator; + } + return true; +} + +_SOKOL_PRIVATE bool _sapp_glx_extsupported(const char* ext, const char* extensions) { + if (extensions) { + return _sapp_glx_has_ext(ext, extensions); + } + else { + return false; + } +} + +_SOKOL_PRIVATE void* _sapp_glx_getprocaddr(const char* procname) +{ + if (_sapp.glx.GetProcAddress) { + return (void*) _sapp.glx.GetProcAddress(procname); + } + else if (_sapp.glx.GetProcAddressARB) { + return (void*) _sapp.glx.GetProcAddressARB(procname); + } + else { + return dlsym(_sapp.glx.libgl, procname); + } +} + +_SOKOL_PRIVATE void _sapp_glx_init(void) { + const char* sonames[] = { "libGL.so.1", "libGL.so", 0 }; + for (int i = 0; sonames[i]; i++) { + _sapp.glx.libgl = dlopen(sonames[i], RTLD_LAZY|RTLD_GLOBAL); + if (_sapp.glx.libgl) { + break; + } + } + if (!_sapp.glx.libgl) { + _SAPP_PANIC(LINUX_GLX_LOAD_LIBGL_FAILED); + } + _sapp.glx.GetFBConfigs = (PFNGLXGETFBCONFIGSPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigs"); + _sapp.glx.GetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) dlsym(_sapp.glx.libgl, "glXGetFBConfigAttrib"); + _sapp.glx.GetClientString = (PFNGLXGETCLIENTSTRINGPROC) dlsym(_sapp.glx.libgl, "glXGetClientString"); + _sapp.glx.QueryExtension = (PFNGLXQUERYEXTENSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryExtension"); + _sapp.glx.QueryVersion = (PFNGLXQUERYVERSIONPROC) dlsym(_sapp.glx.libgl, "glXQueryVersion"); + _sapp.glx.DestroyContext = (PFNGLXDESTROYCONTEXTPROC) dlsym(_sapp.glx.libgl, "glXDestroyContext"); + _sapp.glx.MakeCurrent = (PFNGLXMAKECURRENTPROC) dlsym(_sapp.glx.libgl, "glXMakeCurrent"); + _sapp.glx.SwapBuffers = (PFNGLXSWAPBUFFERSPROC) dlsym(_sapp.glx.libgl, "glXSwapBuffers"); + _sapp.glx.QueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) dlsym(_sapp.glx.libgl, "glXQueryExtensionsString"); + _sapp.glx.CreateWindow = (PFNGLXCREATEWINDOWPROC) dlsym(_sapp.glx.libgl, "glXCreateWindow"); + _sapp.glx.DestroyWindow = (PFNGLXDESTROYWINDOWPROC) dlsym(_sapp.glx.libgl, "glXDestroyWindow"); + _sapp.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddress"); + _sapp.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(_sapp.glx.libgl, "glXGetProcAddressARB"); + _sapp.glx.GetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) dlsym(_sapp.glx.libgl, "glXGetVisualFromFBConfig"); + if (!_sapp.glx.GetFBConfigs || + !_sapp.glx.GetFBConfigAttrib || + !_sapp.glx.GetClientString || + !_sapp.glx.QueryExtension || + !_sapp.glx.QueryVersion || + !_sapp.glx.DestroyContext || + !_sapp.glx.MakeCurrent || + !_sapp.glx.SwapBuffers || + !_sapp.glx.QueryExtensionsString || + !_sapp.glx.CreateWindow || + !_sapp.glx.DestroyWindow || + !_sapp.glx.GetProcAddress || + !_sapp.glx.GetProcAddressARB || + !_sapp.glx.GetVisualFromFBConfig) + { + _SAPP_PANIC(LINUX_GLX_LOAD_ENTRY_POINTS_FAILED); + } + + if (!_sapp.glx.QueryExtension(_sapp.x11.display, &_sapp.glx.error_base, &_sapp.glx.event_base)) { + _SAPP_PANIC(LINUX_GLX_EXTENSION_NOT_FOUND); + } + if (!_sapp.glx.QueryVersion(_sapp.x11.display, &_sapp.glx.major, &_sapp.glx.minor)) { + _SAPP_PANIC(LINUX_GLX_QUERY_VERSION_FAILED); + } + if (_sapp.glx.major == 1 && _sapp.glx.minor < 3) { + _SAPP_PANIC(LINUX_GLX_VERSION_TOO_LOW); + } + const char* exts = _sapp.glx.QueryExtensionsString(_sapp.x11.display, _sapp.x11.screen); + if (_sapp_glx_extsupported("GLX_EXT_swap_control", exts)) { + _sapp.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) _sapp_glx_getprocaddr("glXSwapIntervalEXT"); + _sapp.glx.EXT_swap_control = 0 != _sapp.glx.SwapIntervalEXT; + } + if (_sapp_glx_extsupported("GLX_MESA_swap_control", exts)) { + _sapp.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) _sapp_glx_getprocaddr("glXSwapIntervalMESA"); + _sapp.glx.MESA_swap_control = 0 != _sapp.glx.SwapIntervalMESA; + } + _sapp.glx.ARB_multisample = _sapp_glx_extsupported("GLX_ARB_multisample", exts); + if (_sapp_glx_extsupported("GLX_ARB_create_context", exts)) { + _sapp.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) _sapp_glx_getprocaddr("glXCreateContextAttribsARB"); + _sapp.glx.ARB_create_context = 0 != _sapp.glx.CreateContextAttribsARB; + } + _sapp.glx.ARB_create_context_profile = _sapp_glx_extsupported("GLX_ARB_create_context_profile", exts); +} + +_SOKOL_PRIVATE int _sapp_glx_attrib(GLXFBConfig fbconfig, int attrib) { + int value; + _sapp.glx.GetFBConfigAttrib(_sapp.x11.display, fbconfig, attrib, &value); + return value; +} + +_SOKOL_PRIVATE GLXFBConfig _sapp_glx_choosefbconfig(void) { + GLXFBConfig* native_configs; + _sapp_gl_fbconfig* usable_configs; + const _sapp_gl_fbconfig* closest; + int i, native_count, usable_count; + const char* vendor; + bool trust_window_bit = true; + + /* HACK: This is a (hopefully temporary) workaround for Chromium + (VirtualBox GL) not setting the window bit on any GLXFBConfigs + */ + vendor = _sapp.glx.GetClientString(_sapp.x11.display, GLX_VENDOR); + if (vendor && strcmp(vendor, "Chromium") == 0) { + trust_window_bit = false; + } + + native_configs = _sapp.glx.GetFBConfigs(_sapp.x11.display, _sapp.x11.screen, &native_count); + if (!native_configs || !native_count) { + _SAPP_PANIC(LINUX_GLX_NO_GLXFBCONFIGS); + } + + usable_configs = (_sapp_gl_fbconfig*) _sapp_malloc_clear((size_t)native_count * sizeof(_sapp_gl_fbconfig)); + usable_count = 0; + for (i = 0; i < native_count; i++) { + const GLXFBConfig n = native_configs[i]; + _sapp_gl_fbconfig* u = usable_configs + usable_count; + _sapp_gl_init_fbconfig(u); + + /* Only consider RGBA GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) { + continue; + } + /* Only consider window GLXFBConfigs */ + if (0 == (_sapp_glx_attrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) { + if (trust_window_bit) { + continue; + } + } + u->red_bits = _sapp_glx_attrib(n, GLX_RED_SIZE); + u->green_bits = _sapp_glx_attrib(n, GLX_GREEN_SIZE); + u->blue_bits = _sapp_glx_attrib(n, GLX_BLUE_SIZE); + u->alpha_bits = _sapp_glx_attrib(n, GLX_ALPHA_SIZE); + u->depth_bits = _sapp_glx_attrib(n, GLX_DEPTH_SIZE); + u->stencil_bits = _sapp_glx_attrib(n, GLX_STENCIL_SIZE); + if (_sapp_glx_attrib(n, GLX_DOUBLEBUFFER)) { + u->doublebuffer = true; + } + if (_sapp.glx.ARB_multisample) { + u->samples = _sapp_glx_attrib(n, GLX_SAMPLES); + } + u->handle = (uintptr_t) n; + usable_count++; + } + _sapp_gl_fbconfig desired; + _sapp_gl_init_fbconfig(&desired); + desired.red_bits = 8; + desired.green_bits = 8; + desired.blue_bits = 8; + desired.alpha_bits = 8; + desired.depth_bits = 24; + desired.stencil_bits = 8; + desired.doublebuffer = true; + desired.samples = _sapp.sample_count > 1 ? _sapp.sample_count : 0; + closest = _sapp_gl_choose_fbconfig(&desired, usable_configs, usable_count); + GLXFBConfig result = 0; + if (closest) { + result = (GLXFBConfig) closest->handle; + } + XFree(native_configs); + _sapp_free(usable_configs); + return result; +} + +_SOKOL_PRIVATE void _sapp_glx_choose_visual(Visual** visual, int* depth) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native) { + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); + } + XVisualInfo* result = _sapp.glx.GetVisualFromFBConfig(_sapp.x11.display, native); + if (!result) { + _SAPP_PANIC(LINUX_GLX_GET_VISUAL_FROM_FBCONFIG_FAILED); + } + *visual = result->visual; + *depth = result->depth; + XFree(result); +} + +_SOKOL_PRIVATE void _sapp_glx_make_current(void) { + _sapp.glx.MakeCurrent(_sapp.x11.display, _sapp.glx.window, _sapp.glx.ctx); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); +} + +_SOKOL_PRIVATE void _sapp_glx_create_context(void) { + GLXFBConfig native = _sapp_glx_choosefbconfig(); + if (0 == native){ + _SAPP_PANIC(LINUX_GLX_NO_SUITABLE_GLXFBCONFIG); + } + if (!(_sapp.glx.ARB_create_context && _sapp.glx.ARB_create_context_profile)) { + _SAPP_PANIC(LINUX_GLX_REQUIRED_EXTENSIONS_MISSING); + } + _sapp_x11_grab_error_handler(); + const int attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, _sapp.desc.gl_major_version, + GLX_CONTEXT_MINOR_VERSION_ARB, _sapp.desc.gl_minor_version, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + 0, 0 + }; + _sapp.glx.ctx = _sapp.glx.CreateContextAttribsARB(_sapp.x11.display, native, NULL, True, attribs); + if (!_sapp.glx.ctx) { + _SAPP_PANIC(LINUX_GLX_CREATE_CONTEXT_FAILED); + } + _sapp_x11_release_error_handler(); + _sapp.glx.window = _sapp.glx.CreateWindow(_sapp.x11.display, native, _sapp.x11.window, NULL); + if (!_sapp.glx.window) { + _SAPP_PANIC(LINUX_GLX_CREATE_WINDOW_FAILED); + } + _sapp_glx_make_current(); +} + +_SOKOL_PRIVATE void _sapp_glx_destroy_context(void) { + if (_sapp.glx.window) { + _sapp.glx.DestroyWindow(_sapp.x11.display, _sapp.glx.window); + _sapp.glx.window = 0; + } + if (_sapp.glx.ctx) { + _sapp.glx.DestroyContext(_sapp.x11.display, _sapp.glx.ctx); + _sapp.glx.ctx = 0; + } +} + +_SOKOL_PRIVATE void _sapp_glx_swap_buffers(void) { + _sapp.glx.SwapBuffers(_sapp.x11.display, _sapp.glx.window); +} + +_SOKOL_PRIVATE void _sapp_glx_swapinterval(int interval) { + if (_sapp.glx.EXT_swap_control) { + _sapp.glx.SwapIntervalEXT(_sapp.x11.display, _sapp.glx.window, interval); + } + else if (_sapp.glx.MESA_swap_control) { + _sapp.glx.SwapIntervalMESA(interval); + } +} + +#endif /* _SAPP_GLX */ + +_SOKOL_PRIVATE void _sapp_x11_send_event(Atom type, int a, int b, int c, int d, int e) { + XEvent event; + _sapp_clear(&event, sizeof(event)); + + event.type = ClientMessage; + event.xclient.window = _sapp.x11.window; + event.xclient.format = 32; + event.xclient.message_type = type; + event.xclient.data.l[0] = a; + event.xclient.data.l[1] = b; + event.xclient.data.l[2] = c; + event.xclient.data.l[3] = d; + event.xclient.data.l[4] = e; + + XSendEvent(_sapp.x11.display, _sapp.x11.root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); +} + +_SOKOL_PRIVATE void _sapp_x11_query_window_size(void) { + XWindowAttributes attribs; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &attribs); + _sapp.window_width = attribs.width; + _sapp.window_height = attribs.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; +} + +_SOKOL_PRIVATE void _sapp_x11_set_fullscreen(bool enable) { + /* NOTE: this function must be called after XMapWindow (which happens in _sapp_x11_show_window()) */ + if (_sapp.x11.NET_WM_STATE && _sapp.x11.NET_WM_STATE_FULLSCREEN) { + if (enable) { + const int _NET_WM_STATE_ADD = 1; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_ADD, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + else { + const int _NET_WM_STATE_REMOVE = 0; + _sapp_x11_send_event(_sapp.x11.NET_WM_STATE, + _NET_WM_STATE_REMOVE, + _sapp.x11.NET_WM_STATE_FULLSCREEN, + 0, 1, 0); + } + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { + SOKOL_ASSERT(0 == _sapp.x11.hidden_cursor); + const int w = 16; + const int h = 16; + XcursorImage* img = XcursorImageCreate(w, h); + SOKOL_ASSERT(img && (img->width == 16) && (img->height == 16) && img->pixels); + img->xhot = 0; + img->yhot = 0; + const size_t num_bytes = (size_t)(w * h) * sizeof(XcursorPixel); + _sapp_clear(img->pixels, num_bytes); + _sapp.x11.hidden_cursor = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); +} + + _SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.x11.display); + if (theme) { + XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); + if (img) { + _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + } + } + if (0 == _sapp.x11.cursors[cursor]) { + _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); + } +} + +_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); + const int size = XcursorGetDefaultSize(_sapp.x11.display); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); + _sapp_x11_create_hidden_cursor(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + if (_sapp.x11.hidden_cursor) { + XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); + _sapp.x11.hidden_cursor = 0; + } + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + if (_sapp.x11.cursors[i]) { + XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); + _sapp.x11.cursors[i] = 0; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { + _sapp.fullscreen = !_sapp.fullscreen; + _sapp_x11_set_fullscreen(_sapp.fullscreen); + _sapp_x11_query_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (shown) { + if (_sapp.x11.cursors[cursor]) { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]); + } + else { + XUndefineCursor(_sapp.x11.display, _sapp.x11.window); + } + } + else { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + if (_sapp.mouse.locked) { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; // XIMaskLen is a macro + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISetMask(mask, XI_RawMotion); + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XGrabPointer(_sapp.x11.display, // display + _sapp.x11.window, // grab_window + True, // owner_events + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // event_mask + GrabModeAsync, // pointer_mode + GrabModeAsync, // keyboard_mode + _sapp.x11.window, // confine_to + _sapp.x11.hidden_cursor, // cursor + CurrentTime); // time + } + else { + if (_sapp.x11.xi.available) { + XIEventMask em; + unsigned char mask[] = { 0 }; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISelectEvents(_sapp.x11.display, _sapp.x11.root, &em, 1); + } + XWarpPointer(_sapp.x11.display, None, _sapp.x11.window, 0, 0, 0, 0, (int) _sapp.mouse.x, _sapp.mouse.y); + XUngrabPointer(_sapp.x11.display, CurrentTime); + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_set_clipboard_string(const char* str) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + if (strlen(str) >= (size_t)_sapp.clipboard.buf_size) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + } + XSetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD, _sapp.x11.window, CurrentTime); + if (XGetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD) != _sapp.x11.window) { + _SAPP_ERROR(LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD); + } +} + +_SOKOL_PRIVATE const char* _sapp_x11_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + Atom none = XInternAtom(_sapp.x11.display, "SAPP_SELECTION", False); + Atom incremental = XInternAtom(_sapp.x11.display, "INCR", False); + if (XGetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD) == _sapp.x11.window) { + // Instead of doing a large number of X round-trips just to put this + // string into a window property and then read it back, just return it + return _sapp.clipboard.buffer; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.CLIPBOARD, + _sapp.x11.UTF8_STRING, + none, + _sapp.x11.window, + CurrentTime); + XEvent event; + while (!XCheckTypedWindowEvent(_sapp.x11.display, _sapp.x11.window, SelectionNotify, &event)) { + // Wait for event data to arrive on the X11 display socket + struct pollfd fd = { ConnectionNumber(_sapp.x11.display), POLLIN }; + while (!XPending(_sapp.x11.display)) { + poll(&fd, 1, -1); + } + } + if (event.xselection.property == None) { + return NULL; + } + char* data = NULL; + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + const bool ret = XGetWindowProperty(_sapp.x11.display, + event.xselection.requestor, + event.xselection.property, + 0, + LONG_MAX, + True, + _sapp.x11.UTF8_STRING, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + (unsigned char**) &data); + if (ret != Success || data == NULL) { + if (data != NULL) { + XFree(data); + } + return NULL; + } + if ((actualType == incremental) || (itemCount >= (size_t)_sapp.clipboard.buf_size)) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + XFree(data); + return NULL; + } + _sapp_strcpy(data, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + XFree(data); + return _sapp.clipboard.buffer; +} + +_SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { + Xutf8SetWMProperties(_sapp.x11.display, + _sapp.x11.window, + _sapp.window_title, _sapp.window_title, + NULL, 0, NULL, NULL, NULL); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON_NAME, _sapp.x11.UTF8_STRING, 8, + PropModeReplace, + (unsigned char*)_sapp.window_title, + strlen(_sapp.window_title)); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_set_icon(const sapp_icon_desc* icon_desc, int num_images) { + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + int long_count = 0; + for (int i = 0; i < num_images; i++) { + const sapp_image_desc* img_desc = &icon_desc->images[i]; + long_count += 2 + (img_desc->width * img_desc->height); + } + long* icon_data = (long*) _sapp_malloc_clear((size_t)long_count * sizeof(long)); + SOKOL_ASSERT(icon_data); + long* dst = icon_data; + for (int img_index = 0; img_index < num_images; img_index++) { + const sapp_image_desc* img_desc = &icon_desc->images[img_index]; + const uint8_t* src = (const uint8_t*) img_desc->pixels.ptr; + *dst++ = img_desc->width; + *dst++ = img_desc->height; + const int num_pixels = img_desc->width * img_desc->height; + for (int pixel_index = 0; pixel_index < num_pixels; pixel_index++) { + *dst++ = ((long)(src[pixel_index * 4 + 0]) << 16) | + ((long)(src[pixel_index * 4 + 1]) << 8) | + ((long)(src[pixel_index * 4 + 2]) << 0) | + ((long)(src[pixel_index * 4 + 3]) << 24); + } + } + XChangeProperty(_sapp.x11.display, _sapp.x11.window, + _sapp.x11.NET_WM_ICON, + XA_CARDINAL, 32, + PropModeReplace, + (unsigned char*)icon_data, + long_count); + _sapp_free(icon_data); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE void _sapp_x11_create_window(Visual* visual, int depth) { + _sapp.x11.colormap = XCreateColormap(_sapp.x11.display, _sapp.x11.root, visual, AllocNone); + XSetWindowAttributes wa; + _sapp_clear(&wa, sizeof(wa)); + const uint32_t wamask = CWBorderPixel | CWColormap | CWEventMask; + wa.colormap = _sapp.x11.colormap; + wa.border_pixel = 0; + wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | + PointerMotionMask | ButtonPressMask | ButtonReleaseMask | + ExposureMask | FocusChangeMask | VisibilityChangeMask | + EnterWindowMask | LeaveWindowMask | PropertyChangeMask; + + int display_width = DisplayWidth(_sapp.x11.display, _sapp.x11.screen); + int display_height = DisplayHeight(_sapp.x11.display, _sapp.x11.screen); + int window_width = _sapp.window_width; + int window_height = _sapp.window_height; + if (0 == window_width) { + window_width = (display_width * 4) / 5; + } + if (0 == window_height) { + window_height = (display_height * 4) / 5; + } + int window_xpos = (display_width - window_width) / 2; + int window_ypos = (display_height - window_height) / 2; + if (window_xpos < 0) { + window_xpos = 0; + } + if (window_ypos < 0) { + window_ypos = 0; + } + _sapp_x11_grab_error_handler(); + _sapp.x11.window = XCreateWindow(_sapp.x11.display, + _sapp.x11.root, + window_xpos, + window_ypos, + (uint32_t)window_width, + (uint32_t)window_height, + 0, /* border width */ + depth, /* color depth */ + InputOutput, + visual, + wamask, + &wa); + _sapp_x11_release_error_handler(); + if (!_sapp.x11.window) { + _SAPP_PANIC(LINUX_X11_CREATE_WINDOW_FAILED); + } + Atom protocols[] = { + _sapp.x11.WM_DELETE_WINDOW + }; + XSetWMProtocols(_sapp.x11.display, _sapp.x11.window, protocols, 1); + + XSizeHints* hints = XAllocSizeHints(); + hints->flags = (PWinGravity | PPosition | PSize); + hints->win_gravity = StaticGravity; + hints->x = window_xpos; + hints->y = window_ypos; + hints->width = window_width; + hints->height = window_height; + XSetWMNormalHints(_sapp.x11.display, _sapp.x11.window, hints); + XFree(hints); + + /* announce support for drag'n'drop */ + if (_sapp.drop.enabled) { + const Atom version = _SAPP_X11_XDND_VERSION; + XChangeProperty(_sapp.x11.display, _sapp.x11.window, _sapp.x11.xdnd.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); + } + _sapp_x11_update_window_title(); + _sapp_x11_query_window_size(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_window(void) { + if (_sapp.x11.window) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XDestroyWindow(_sapp.x11.display, _sapp.x11.window); + _sapp.x11.window = 0; + } + if (_sapp.x11.colormap) { + XFreeColormap(_sapp.x11.display, _sapp.x11.colormap); + _sapp.x11.colormap = 0; + } + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE bool _sapp_x11_window_visible(void) { + XWindowAttributes wa; + XGetWindowAttributes(_sapp.x11.display, _sapp.x11.window, &wa); + return wa.map_state == IsViewable; +} + +_SOKOL_PRIVATE void _sapp_x11_show_window(void) { + if (!_sapp_x11_window_visible()) { + XMapWindow(_sapp.x11.display, _sapp.x11.window); + XRaiseWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); + } +} + +_SOKOL_PRIVATE void _sapp_x11_hide_window(void) { + XUnmapWindow(_sapp.x11.display, _sapp.x11.window); + XFlush(_sapp.x11.display); +} + +_SOKOL_PRIVATE unsigned long _sapp_x11_get_window_property(Window window, Atom property, Atom type, unsigned char** value) { + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + XGetWindowProperty(_sapp.x11.display, + window, + property, + 0, + LONG_MAX, + False, + type, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + value); + return itemCount; +} + +_SOKOL_PRIVATE int _sapp_x11_get_window_state(void) { + int result = WithdrawnState; + struct { + CARD32 state; + Window icon; + } *state = NULL; + + if (_sapp_x11_get_window_property(_sapp.x11.window, _sapp.x11.WM_STATE, _sapp.x11.WM_STATE, (unsigned char**)&state) >= 2) { + result = (int)state->state; + } + if (state) { + XFree(state); + } + return result; +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_key_modifier_bit(sapp_keycode key) { + switch (key) { + case SAPP_KEYCODE_LEFT_SHIFT: + case SAPP_KEYCODE_RIGHT_SHIFT: + return SAPP_MODIFIER_SHIFT; + case SAPP_KEYCODE_LEFT_CONTROL: + case SAPP_KEYCODE_RIGHT_CONTROL: + return SAPP_MODIFIER_CTRL; + case SAPP_KEYCODE_LEFT_ALT: + case SAPP_KEYCODE_RIGHT_ALT: + return SAPP_MODIFIER_ALT; + case SAPP_KEYCODE_LEFT_SUPER: + case SAPP_KEYCODE_RIGHT_SUPER: + return SAPP_MODIFIER_SUPER; + default: + return 0; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_button_modifier_bit(sapp_mousebutton btn) { + switch (btn) { + case SAPP_MOUSEBUTTON_LEFT: return SAPP_MODIFIER_LMB; + case SAPP_MOUSEBUTTON_RIGHT: return SAPP_MODIFIER_RMB; + case SAPP_MOUSEBUTTON_MIDDLE: return SAPP_MODIFIER_MMB; + default: return 0; + } +} + +_SOKOL_PRIVATE uint32_t _sapp_x11_mods(uint32_t x11_mods) { + uint32_t mods = 0; + if (x11_mods & ShiftMask) { + mods |= SAPP_MODIFIER_SHIFT; + } + if (x11_mods & ControlMask) { + mods |= SAPP_MODIFIER_CTRL; + } + if (x11_mods & Mod1Mask) { + mods |= SAPP_MODIFIER_ALT; + } + if (x11_mods & Mod4Mask) { + mods |= SAPP_MODIFIER_SUPER; + } + if (x11_mods & Button1Mask) { + mods |= SAPP_MODIFIER_LMB; + } + if (x11_mods & Button2Mask) { + mods |= SAPP_MODIFIER_MMB; + } + if (x11_mods & Button3Mask) { + mods |= SAPP_MODIFIER_RMB; + } + return mods; +} + +_SOKOL_PRIVATE void _sapp_x11_app_event(sapp_event_type type) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_mousebutton _sapp_x11_translate_button(const XEvent* event) { + switch (event->xbutton.button) { + case Button1: return SAPP_MOUSEBUTTON_LEFT; + case Button2: return SAPP_MOUSEBUTTON_MIDDLE; + case Button3: return SAPP_MOUSEBUTTON_RIGHT; + default: return SAPP_MOUSEBUTTON_INVALID; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_update(int x, int y, bool clear_dxdy) { + if (!_sapp.mouse.locked) { + const float new_x = (float) x; + const float new_y = (float) y; + if (clear_dxdy) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + } else if (_sapp.mouse.pos_valid) { + _sapp.mouse.dx = new_x - _sapp.mouse.x; + _sapp.mouse.dy = new_y - _sapp.mouse.y; + } + _sapp.mouse.x = new_x; + _sapp.mouse.y = new_y; + _sapp.mouse.pos_valid = true; + } +} + +_SOKOL_PRIVATE void _sapp_x11_mouse_event(sapp_event_type type, sapp_mousebutton btn, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.mouse_button = btn; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_scroll_event(float x, float y, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_MOUSE_SCROLL); + _sapp.event.modifiers = mods; + _sapp.event.scroll_x = x; + _sapp.event.scroll_y = y; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE void _sapp_x11_key_event(sapp_event_type type, sapp_keycode key, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(type); + _sapp.event.key_code = key; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + /* check if a CLIPBOARD_PASTED event must be sent too */ + if (_sapp.clipboard.enabled && + (type == SAPP_EVENTTYPE_KEY_DOWN) && + (_sapp.event.modifiers == SAPP_MODIFIER_CTRL) && + (_sapp.event.key_code == SAPP_KEYCODE_V)) + { + _sapp_init_event(SAPP_EVENTTYPE_CLIPBOARD_PASTED); + _sapp_call_event(&_sapp.event); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_char_event(uint32_t chr, bool repeat, uint32_t mods) { + if (_sapp_events_enabled()) { + _sapp_init_event(SAPP_EVENTTYPE_CHAR); + _sapp.event.char_code = chr; + _sapp.event.key_repeat = repeat; + _sapp.event.modifiers = mods; + _sapp_call_event(&_sapp.event); + } +} + +_SOKOL_PRIVATE sapp_keycode _sapp_x11_translate_key(int scancode) { + if ((scancode >= 0) && (scancode < _SAPP_X11_MAX_X11_KEYCODES)) { + return _sapp.keycodes[scancode]; + } else { + return SAPP_KEYCODE_INVALID; + } +} + +_SOKOL_PRIVATE int32_t _sapp_x11_keysym_to_unicode(KeySym keysym) { + int min = 0; + int max = sizeof(_sapp_x11_keysymtab) / sizeof(struct _sapp_x11_codepair) - 1; + int mid; + + /* First check for Latin-1 characters (1:1 mapping) */ + if ((keysym >= 0x0020 && keysym <= 0x007e) || + (keysym >= 0x00a0 && keysym <= 0x00ff)) + { + return keysym; + } + + /* Also check for directly encoded 24-bit UCS characters */ + if ((keysym & 0xff000000) == 0x01000000) { + return keysym & 0x00ffffff; + } + + /* Binary search in table */ + while (max >= min) { + mid = (min + max) / 2; + if (_sapp_x11_keysymtab[mid].keysym < keysym) { + min = mid + 1; + } + else if (_sapp_x11_keysymtab[mid].keysym > keysym) { + max = mid - 1; + } + else { + return _sapp_x11_keysymtab[mid].ucs; + } + } + + /* No matching Unicode value found */ + return -1; +} + +_SOKOL_PRIVATE bool _sapp_x11_keypress_repeat(int keycode) { + bool repeat = false; + if ((keycode >= 0) && (keycode < _SAPP_X11_MAX_X11_KEYCODES)) { + repeat = _sapp.x11.key_repeat[keycode]; + _sapp.x11.key_repeat[keycode] = true; + } + return repeat; +} + +_SOKOL_PRIVATE void _sapp_x11_keyrelease_repeat(int keycode) { + if ((keycode >= 0) && (keycode < _SAPP_X11_MAX_X11_KEYCODES)) { + _sapp.x11.key_repeat[keycode] = false; + } +} + +_SOKOL_PRIVATE bool _sapp_x11_parse_dropped_files_list(const char* src) { + SOKOL_ASSERT(src); + SOKOL_ASSERT(_sapp.drop.buffer); + + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + + /* + src is (potentially percent-encoded) string made of one or multiple paths + separated by \r\n, each path starting with 'file://' + */ + bool err = false; + int src_count = 0; + char src_chr = 0; + char* dst_ptr = _sapp.drop.buffer; + const char* dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); // room for terminating 0 + while (0 != (src_chr = *src++)) { + src_count++; + char dst_chr = 0; + /* check leading 'file://' */ + if (src_count <= 7) { + if (((src_count == 1) && (src_chr != 'f')) || + ((src_count == 2) && (src_chr != 'i')) || + ((src_count == 3) && (src_chr != 'l')) || + ((src_count == 4) && (src_chr != 'e')) || + ((src_count == 5) && (src_chr != ':')) || + ((src_count == 6) && (src_chr != '/')) || + ((src_count == 7) && (src_chr != '/'))) + { + _SAPP_ERROR(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME); + err = true; + break; + } + } + else if (src_chr == '\r') { + // skip + } + else if (src_chr == '\n') { + src_count = 0; + _sapp.drop.num_files++; + // too many files is not an error + if (_sapp.drop.num_files >= _sapp.drop.max_files) { + break; + } + dst_ptr = _sapp.drop.buffer + _sapp.drop.num_files * _sapp.drop.max_path_length; + dst_end_ptr = dst_ptr + (_sapp.drop.max_path_length - 1); + } + else if ((src_chr == '%') && src[0] && src[1]) { + // a percent-encoded byte (most likely UTF-8 multibyte sequence) + const char digits[3] = { src[0], src[1], 0 }; + src += 2; + dst_chr = (char) strtol(digits, 0, 16); + } + else { + dst_chr = src_chr; + } + if (dst_chr) { + // dst_end_ptr already has adjustment for terminating zero + if (dst_ptr < dst_end_ptr) { + *dst_ptr++ = dst_chr; + } + else { + _SAPP_ERROR(DROPPED_FILE_PATH_TOO_LONG); + err = true; + break; + } + } + } + if (err) { + _sapp_clear_drop_buffer(); + _sapp.drop.num_files = 0; + return false; + } + else { + return true; + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_genericevent(XEvent* event) { + if (_sapp.mouse.locked && _sapp.x11.xi.available) { + if (event->xcookie.extension == _sapp.x11.xi.major_opcode) { + if (XGetEventData(_sapp.x11.display, &event->xcookie)) { + if (event->xcookie.evtype == XI_RawMotion) { + XIRawEvent* re = (XIRawEvent*) event->xcookie.data; + if (re->valuators.mask_len) { + const double* values = re->raw_values; + if (XIMaskIsSet(re->valuators.mask, 0)) { + _sapp.mouse.dx = (float) *values; + values++; + } + if (XIMaskIsSet(re->valuators.mask, 1)) { + _sapp.mouse.dy = (float) *values; + } + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); + } + } + XFreeEventData(_sapp.x11.display, &event->xcookie); + } + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_focusin(XEvent* event) { + // NOTE: ignoring NotifyGrab and NotifyUngrab is same behaviour as GLFW + if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { + _sapp_x11_app_event(SAPP_EVENTTYPE_FOCUSED); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_focusout(XEvent* event) { + // if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock + if (_sapp.mouse.locked) { + _sapp_x11_lock_mouse(false); + } + // NOTE: ignoring NotifyGrab and NotifyUngrab is same behaviour as GLFW + if ((event->xfocus.mode != NotifyGrab) && (event->xfocus.mode != NotifyUngrab)) { + _sapp_x11_app_event(SAPP_EVENTTYPE_UNFOCUSED); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_keypress(XEvent* event) { + int keycode = (int)event->xkey.keycode; + + const sapp_keycode key = _sapp_x11_translate_key(keycode); + const bool repeat = _sapp_x11_keypress_repeat(keycode); + uint32_t mods = _sapp_x11_mods(event->xkey.state); + // X11 doesn't set modifier bit on key down, so emulate that + mods |= _sapp_x11_key_modifier_bit(key); + if (key != SAPP_KEYCODE_INVALID) { + _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_DOWN, key, repeat, mods); + } + KeySym keysym; + XLookupString(&event->xkey, NULL, 0, &keysym, NULL); + int32_t chr = _sapp_x11_keysym_to_unicode(keysym); + if (chr > 0) { + _sapp_x11_char_event((uint32_t)chr, repeat, mods); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_keyrelease(XEvent* event) { + int keycode = (int)event->xkey.keycode; + const sapp_keycode key = _sapp_x11_translate_key(keycode); + _sapp_x11_keyrelease_repeat(keycode); + if (key != SAPP_KEYCODE_INVALID) { + uint32_t mods = _sapp_x11_mods(event->xkey.state); + // X11 doesn't clear modifier bit on key up, so emulate that + mods &= ~_sapp_x11_key_modifier_bit(key); + _sapp_x11_key_event(SAPP_EVENTTYPE_KEY_UP, key, false, mods); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_buttonpress(XEvent* event) { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + uint32_t mods = _sapp_x11_mods(event->xbutton.state); + // X11 doesn't set modifier bit on button down, so emulate that + mods |= _sapp_x11_button_modifier_bit(btn); + if (btn != SAPP_MOUSEBUTTON_INVALID) { + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_DOWN, btn, mods); + _sapp.x11.mouse_buttons |= (1 << btn); + } + else { + // might be a scroll event + switch (event->xbutton.button) { + case 4: _sapp_x11_scroll_event(0.0f, 1.0f, mods); break; + case 5: _sapp_x11_scroll_event(0.0f, -1.0f, mods); break; + case 6: _sapp_x11_scroll_event(1.0f, 0.0f, mods); break; + case 7: _sapp_x11_scroll_event(-1.0f, 0.0f, mods); break; + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_buttonrelease(XEvent* event) { + _sapp_x11_mouse_update(event->xbutton.x, event->xbutton.y, false); + const sapp_mousebutton btn = _sapp_x11_translate_button(event); + if (btn != SAPP_MOUSEBUTTON_INVALID) { + uint32_t mods = _sapp_x11_mods(event->xbutton.state); + // X11 doesn't clear modifier bit on button up, so emulate that + mods &= ~_sapp_x11_button_modifier_bit(btn); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_UP, btn, mods); + _sapp.x11.mouse_buttons &= ~(1 << btn); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_enternotify(XEvent* event) { + // don't send enter/leave events while mouse button held down + if (0 == _sapp.x11.mouse_buttons) { + _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y, true); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_ENTER, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_leavenotify(XEvent* event) { + if (0 == _sapp.x11.mouse_buttons) { + _sapp_x11_mouse_update(event->xcrossing.x, event->xcrossing.y, true); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_LEAVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xcrossing.state)); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_motionnotify(XEvent* event) { + if (!_sapp.mouse.locked) { + _sapp_x11_mouse_update(event->xmotion.x, event->xmotion.y, false); + _sapp_x11_mouse_event(SAPP_EVENTTYPE_MOUSE_MOVE, SAPP_MOUSEBUTTON_INVALID, _sapp_x11_mods(event->xmotion.state)); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_configurenotify(XEvent* event) { + if ((event->xconfigure.width != _sapp.window_width) || (event->xconfigure.height != _sapp.window_height)) { + _sapp.window_width = event->xconfigure.width; + _sapp.window_height = event->xconfigure.height; + _sapp.framebuffer_width = _sapp.window_width; + _sapp.framebuffer_height = _sapp.window_height; + _sapp_x11_app_event(SAPP_EVENTTYPE_RESIZED); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_propertynotify(XEvent* event) { + if (event->xproperty.state == PropertyNewValue) { + if (event->xproperty.atom == _sapp.x11.WM_STATE) { + const int state = _sapp_x11_get_window_state(); + if (state != _sapp.x11.window_state) { + _sapp.x11.window_state = state; + if (state == IconicState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_ICONIFIED); + } + else if (state == NormalState) { + _sapp_x11_app_event(SAPP_EVENTTYPE_RESTORED); + } + } + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_selectionnotify(XEvent* event) { + if (event->xselection.property == _sapp.x11.xdnd.XdndSelection) { + char* data = 0; + uint32_t result = _sapp_x11_get_window_property(event->xselection.requestor, + event->xselection.property, + event->xselection.target, + (unsigned char**) &data); + if (_sapp.drop.enabled && result) { + if (_sapp_x11_parse_dropped_files_list(data)) { + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + if (_sapp_events_enabled()) { + // FIXME: Figure out how to get modifier key state here. + // The XSelection event has no 'state' item, and + // XQueryKeymap() always returns a zeroed array. + _sapp_init_event(SAPP_EVENTTYPE_FILES_DROPPED); + _sapp_call_event(&_sapp.event); + } + } + } + if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = result; + reply.xclient.data.l[2] = (long)_sapp.x11.xdnd.XdndActionCopy; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + if (data) { + XFree(data); + } + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_clientmessage(XEvent* event) { + if (XFilterEvent(event, None)) { + return; + } + if (event->xclient.message_type == _sapp.x11.WM_PROTOCOLS) { + const Atom protocol = (Atom)event->xclient.data.l[0]; + if (protocol == _sapp.x11.WM_DELETE_WINDOW) { + _sapp.quit_requested = true; + } + } else if (event->xclient.message_type == _sapp.x11.xdnd.XdndEnter) { + const bool is_list = 0 != (event->xclient.data.l[1] & 1); + _sapp.x11.xdnd.source = (Window)event->xclient.data.l[0]; + _sapp.x11.xdnd.version = event->xclient.data.l[1] >> 24; + _sapp.x11.xdnd.format = None; + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + uint32_t count = 0; + Atom* formats = 0; + if (is_list) { + count = _sapp_x11_get_window_property(_sapp.x11.xdnd.source, _sapp.x11.xdnd.XdndTypeList, XA_ATOM, (unsigned char**)&formats); + } else { + count = 3; + formats = (Atom*) event->xclient.data.l + 2; + } + for (uint32_t i = 0; i < count; i++) { + if (formats[i] == _sapp.x11.xdnd.text_uri_list) { + _sapp.x11.xdnd.format = _sapp.x11.xdnd.text_uri_list; + break; + } + } + if (is_list && formats) { + XFree(formats); + } + } else if (event->xclient.message_type == _sapp.x11.xdnd.XdndDrop) { + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + Time time = CurrentTime; + if (_sapp.x11.xdnd.format) { + if (_sapp.x11.xdnd.version >= 1) { + time = (Time)event->xclient.data.l[2]; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.xdnd.format, + _sapp.x11.xdnd.XdndSelection, + _sapp.x11.window, + time); + } else if (_sapp.x11.xdnd.version >= 2) { + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndFinished; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + reply.xclient.data.l[1] = 0; // drag was rejected + reply.xclient.data.l[2] = None; + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } + } else if (event->xclient.message_type == _sapp.x11.xdnd.XdndPosition) { + // drag operation has moved over the window + // FIXME: we could track the mouse position here, but + // this isn't implemented on other platforms either so far + if (_sapp.x11.xdnd.version > _SAPP_X11_XDND_VERSION) { + return; + } + XEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = ClientMessage; + reply.xclient.window = _sapp.x11.xdnd.source; + reply.xclient.message_type = _sapp.x11.xdnd.XdndStatus; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)_sapp.x11.window; + if (_sapp.x11.xdnd.format) { + /* reply that we are ready to copy the dragged data */ + reply.xclient.data.l[1] = 1; // accept with no rectangle + if (_sapp.x11.xdnd.version >= 2) { + reply.xclient.data.l[4] = (long)_sapp.x11.xdnd.XdndActionCopy; + } + } + XSendEvent(_sapp.x11.display, _sapp.x11.xdnd.source, False, NoEventMask, &reply); + XFlush(_sapp.x11.display); + } +} + +_SOKOL_PRIVATE void _sapp_x11_on_selectionrequest(XEvent* event) { + XSelectionRequestEvent* req = &event->xselectionrequest; + if (req->selection != _sapp.x11.CLIPBOARD) { + return; + } + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(_sapp.clipboard.buffer); + XSelectionEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = SelectionNotify; + reply.display = req->display; + reply.requestor = req->requestor; + reply.selection = req->selection; + reply.target = req->target; + reply.property = req->property; + reply.time = req->time; + if (req->target == _sapp.x11.UTF8_STRING) { + XChangeProperty(_sapp.x11.display, + req->requestor, + req->property, + _sapp.x11.UTF8_STRING, + 8, + PropModeReplace, + (unsigned char*) _sapp.clipboard.buffer, + strlen(_sapp.clipboard.buffer)); + } else if (req->target == _sapp.x11.TARGETS) { + XChangeProperty(_sapp.x11.display, + req->requestor, + req->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*) &_sapp.x11.UTF8_STRING, + 1); + } else { + reply.property = None; + } + XSendEvent(_sapp.x11.display, req->requestor, False, 0, (XEvent*) &reply); +} + +_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { + switch (event->type) { + case GenericEvent: + _sapp_x11_on_genericevent(event); + break; + case FocusIn: + _sapp_x11_on_focusin(event); + break; + case FocusOut: + _sapp_x11_on_focusout(event); + break; + case KeyPress: + _sapp_x11_on_keypress(event); + break; + case KeyRelease: + _sapp_x11_on_keyrelease(event); + break; + case ButtonPress: + _sapp_x11_on_buttonpress(event); + break; + case ButtonRelease: + _sapp_x11_on_buttonrelease(event); + break; + case EnterNotify: + _sapp_x11_on_enternotify(event); + break; + case LeaveNotify: + _sapp_x11_on_leavenotify(event); + break; + case MotionNotify: + _sapp_x11_on_motionnotify(event); + break; + case ConfigureNotify: + _sapp_x11_on_configurenotify(event); + break; + case PropertyNotify: + _sapp_x11_on_propertynotify(event); + break; + case SelectionNotify: + _sapp_x11_on_selectionnotify(event); + break; + case SelectionRequest: + _sapp_x11_on_selectionrequest(event); + break; + case DestroyNotify: + // not a bug + break; + case ClientMessage: + _sapp_x11_on_clientmessage(event); + break; + } +} + +#if !defined(_SAPP_GLX) + +_SOKOL_PRIVATE void _sapp_egl_init(void) { +#if defined(SOKOL_GLCORE) + if (!eglBindAPI(EGL_OPENGL_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); + } +#else + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_ES_API_FAILED); + } +#endif + + _sapp.egl.display = eglGetDisplay((EGLNativeDisplayType)_sapp.x11.display); + if (EGL_NO_DISPLAY == _sapp.egl.display) { + _SAPP_PANIC(LINUX_EGL_GET_DISPLAY_FAILED); + } + + EGLint major, minor; + if (!eglInitialize(_sapp.egl.display, &major, &minor)) { + _SAPP_PANIC(LINUX_EGL_INITIALIZE_FAILED); + } + + EGLint sample_count = _sapp.desc.sample_count > 1 ? _sapp.desc.sample_count : 0; + EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; + const EGLint config_attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + #if defined(SOKOL_GLCORE) + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + #elif defined(SOKOL_GLES3) + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + #endif + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, alpha_size, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, _sapp.desc.sample_count > 1 ? 1 : 0, + EGL_SAMPLES, sample_count, + EGL_NONE, + }; + + EGLConfig egl_configs[32]; + EGLint config_count; + if (!eglChooseConfig(_sapp.egl.display, config_attrs, egl_configs, 32, &config_count) || config_count == 0) { + _SAPP_PANIC(LINUX_EGL_NO_CONFIGS); + } + + EGLConfig config = egl_configs[0]; + for (int i = 0; i < config_count; ++i) { + EGLConfig c = egl_configs[i]; + EGLint r, g, b, a, d, s, n; + if (eglGetConfigAttrib(_sapp.egl.display, c, EGL_RED_SIZE, &r) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_GREEN_SIZE, &g) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_BLUE_SIZE, &b) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_ALPHA_SIZE, &a) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_DEPTH_SIZE, &d) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_STENCIL_SIZE, &s) && + eglGetConfigAttrib(_sapp.egl.display, c, EGL_SAMPLES, &n) && + (r == 8) && (g == 8) && (b == 8) && (a == alpha_size) && (d == 24) && (s == 8) && (n == sample_count)) { + config = c; + break; + } + } + + EGLint visual_id; + if (!eglGetConfigAttrib(_sapp.egl.display, config, EGL_NATIVE_VISUAL_ID, &visual_id)) { + _SAPP_PANIC(LINUX_EGL_NO_NATIVE_VISUAL); + } + + XVisualInfo visual_info_template; + _sapp_clear(&visual_info_template, sizeof(visual_info_template)); + visual_info_template.visualid = (VisualID)visual_id; + + int num_visuals; + XVisualInfo* visual_info = XGetVisualInfo(_sapp.x11.display, VisualIDMask, &visual_info_template, &num_visuals); + if (!visual_info) { + _SAPP_PANIC(LINUX_EGL_GET_VISUAL_INFO_FAILED); + } + + _sapp_x11_create_window(visual_info->visual, visual_info->depth); + XFree(visual_info); + + _sapp.egl.surface = eglCreateWindowSurface(_sapp.egl.display, config, (EGLNativeWindowType)_sapp.x11.window, NULL); + if (EGL_NO_SURFACE == _sapp.egl.surface) { + _SAPP_PANIC(LINUX_EGL_CREATE_WINDOW_SURFACE_FAILED); + } + + EGLint ctx_attrs[] = { + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + #if defined(SOKOL_GLCORE) + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + #endif + EGL_NONE, + }; + + _sapp.egl.context = eglCreateContext(_sapp.egl.display, config, EGL_NO_CONTEXT, ctx_attrs); + if (EGL_NO_CONTEXT == _sapp.egl.context) { + _SAPP_PANIC(LINUX_EGL_CREATE_CONTEXT_FAILED); + } + + if (!eglMakeCurrent(_sapp.egl.display, _sapp.egl.surface, _sapp.egl.surface, _sapp.egl.context)) { + _SAPP_PANIC(LINUX_EGL_MAKE_CURRENT_FAILED); + } + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_sapp.gl.framebuffer); + + eglSwapInterval(_sapp.egl.display, _sapp.swap_interval); +} + +_SOKOL_PRIVATE void _sapp_egl_destroy(void) { + if (_sapp.egl.display != EGL_NO_DISPLAY) { + eglMakeCurrent(_sapp.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (_sapp.egl.context != EGL_NO_CONTEXT) { + eglDestroyContext(_sapp.egl.display, _sapp.egl.context); + _sapp.egl.context = EGL_NO_CONTEXT; + } + + if (_sapp.egl.surface != EGL_NO_SURFACE) { + eglDestroySurface(_sapp.egl.display, _sapp.egl.surface); + _sapp.egl.surface = EGL_NO_SURFACE; + } + + eglTerminate(_sapp.egl.display); + _sapp.egl.display = EGL_NO_DISPLAY; + } +} + +#endif /* _SAPP_GLX */ + +_SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { + /* The following lines are here to trigger a linker error instead of an + obscure runtime error if the user has forgotten to add -pthread to + the compiler or linker options. They have no other purpose. + */ + pthread_attr_t pthread_attr; + pthread_attr_init(&pthread_attr); + pthread_attr_destroy(&pthread_attr); + + _sapp_init_state(desc); + _sapp.x11.window_state = NormalState; + + XInitThreads(); + XrmInitialize(); + _sapp.x11.display = XOpenDisplay(NULL); + if (!_sapp.x11.display) { + _SAPP_PANIC(LINUX_X11_OPEN_DISPLAY_FAILED); + } + _sapp.x11.screen = DefaultScreen(_sapp.x11.display); + _sapp.x11.root = DefaultRootWindow(_sapp.x11.display); + _sapp_x11_query_system_dpi(); + _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; + _sapp_x11_init_extensions(); + _sapp_x11_create_cursors(); + XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); + _sapp_x11_init_keytable(); +#if defined(_SAPP_GLX) + _sapp_glx_init(); + Visual* visual = 0; + int depth = 0; + _sapp_glx_choose_visual(&visual, &depth); + _sapp_x11_create_window(visual, depth); + _sapp_glx_create_context(); + _sapp_glx_swapinterval(_sapp.swap_interval); +#else + _sapp_egl_init(); +#endif + sapp_set_icon(&desc->icon); + _sapp.valid = true; + _sapp_x11_show_window(); + if (_sapp.fullscreen) { + _sapp_x11_set_fullscreen(true); + } + + XFlush(_sapp.x11.display); + while (!_sapp.quit_ordered) { + _sapp_timing_measure(&_sapp.timing); + int count = XPending(_sapp.x11.display); + while (count--) { + XEvent event; + XNextEvent(_sapp.x11.display, &event); + _sapp_x11_process_event(&event); + } + _sapp_frame(); +#if defined(_SAPP_GLX) + _sapp_glx_swap_buffers(); +#else + eglSwapBuffers(_sapp.egl.display, _sapp.egl.surface); +#endif + XFlush(_sapp.x11.display); + /* handle quit-requested, either from window or from sapp_request_quit() */ + if (_sapp.quit_requested && !_sapp.quit_ordered) { + /* give user code a chance to intervene */ + _sapp_x11_app_event(SAPP_EVENTTYPE_QUIT_REQUESTED); + /* if user code hasn't intervened, quit the app */ + if (_sapp.quit_requested) { + _sapp.quit_ordered = true; + } + } + } + _sapp_call_cleanup(); +#if defined(_SAPP_GLX) + _sapp_glx_destroy_context(); +#else + _sapp_egl_destroy(); +#endif + _sapp_x11_destroy_window(); + _sapp_x11_destroy_cursors(); + XCloseDisplay(_sapp.x11.display); + _sapp_discard_state(); +} + +#if !defined(SOKOL_NO_ENTRY) +int main(int argc, char* argv[]) { + sapp_desc desc = sokol_main(argc, argv); + _sapp_linux_run(&desc); + return 0; +} +#endif /* SOKOL_NO_ENTRY */ +#endif /* _SAPP_LINUX */ + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +#if defined(SOKOL_NO_ENTRY) +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + SOKOL_ASSERT(desc); + #if defined(_SAPP_MACOS) + _sapp_macos_run(desc); + #elif defined(_SAPP_IOS) + _sapp_ios_run(desc); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_run(desc); + #elif defined(_SAPP_WIN32) + _sapp_win32_run(desc); + #elif defined(_SAPP_LINUX) + _sapp_linux_run(desc); + #else + #error "sapp_run() not supported on this platform" + #endif +} + +/* this is just a stub so the linker doesn't complain */ +sapp_desc sokol_main(int argc, char* argv[]) { + _SOKOL_UNUSED(argc); + _SOKOL_UNUSED(argv); + sapp_desc desc; + _sapp_clear(&desc, sizeof(desc)); + return desc; +} +#else +/* likewise, in normal mode, sapp_run() is just an empty stub */ +SOKOL_API_IMPL void sapp_run(const sapp_desc* desc) { + _SOKOL_UNUSED(desc); +} +#endif + +SOKOL_API_IMPL bool sapp_isvalid(void) { + return _sapp.valid; +} + +SOKOL_API_IMPL void* sapp_userdata(void) { + return _sapp.desc.user_data; +} + +SOKOL_API_IMPL sapp_desc sapp_query_desc(void) { + return _sapp.desc; +} + +SOKOL_API_IMPL uint64_t sapp_frame_count(void) { + return _sapp.frame_count; +} + +SOKOL_API_IMPL double sapp_frame_duration(void) { + return _sapp_timing_get_avg(&_sapp.timing); +} + +SOKOL_API_IMPL int sapp_width(void) { + return (_sapp.framebuffer_width > 0) ? _sapp.framebuffer_width : 1; +} + +SOKOL_API_IMPL float sapp_widthf(void) { + return (float)sapp_width(); +} + +SOKOL_API_IMPL int sapp_height(void) { + return (_sapp.framebuffer_height > 0) ? _sapp.framebuffer_height : 1; +} + +SOKOL_API_IMPL float sapp_heightf(void) { + return (float)sapp_height(); +} + +SOKOL_API_IMPL int sapp_color_format(void) { + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + switch (_sapp.wgpu.render_format) { + case WGPUTextureFormat_RGBA8Unorm: + return _SAPP_PIXELFORMAT_RGBA8; + case WGPUTextureFormat_BGRA8Unorm: + return _SAPP_PIXELFORMAT_BGRA8; + default: + SOKOL_UNREACHABLE; + return 0; + } + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + return _SAPP_PIXELFORMAT_BGRA8; + #else + return _SAPP_PIXELFORMAT_RGBA8; + #endif +} + +SOKOL_API_IMPL int sapp_depth_format(void) { + return _SAPP_PIXELFORMAT_DEPTH_STENCIL; +} + +SOKOL_API_IMPL int sapp_sample_count(void) { + return _sapp.sample_count; +} + +SOKOL_API_IMPL bool sapp_high_dpi(void) { + return _sapp.desc.high_dpi && (_sapp.dpi_scale >= 1.5f); +} + +SOKOL_API_IMPL float sapp_dpi_scale(void) { + return _sapp.dpi_scale; +} + +SOKOL_API_IMPL const void* sapp_egl_get_display(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.display; + #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) + return _sapp.egl.display; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_egl_get_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANDROID) + return _sapp.android.context; + #elif defined(_SAPP_LINUX) && !defined(_SAPP_GLX) + return _sapp.egl.context; + #else + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_show_keyboard(bool show) { + #if defined(_SAPP_IOS) + _sapp_ios_show_keyboard(show); + #elif defined(_SAPP_ANDROID) + _sapp_android_show_keyboard(show); + #else + _SOKOL_UNUSED(show); + #endif +} + +SOKOL_API_IMPL bool sapp_keyboard_shown(void) { + return _sapp.onscreen_keyboard_shown; +} + +SOKOL_API_IMPL bool sapp_is_fullscreen(void) { + return _sapp.fullscreen; +} + +SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { + #if defined(_SAPP_MACOS) + _sapp_macos_toggle_fullscreen(); + #elif defined(_SAPP_WIN32) + _sapp_win32_toggle_fullscreen(); + #elif defined(_SAPP_LINUX) + _sapp_x11_toggle_fullscreen(); + #endif +} + +/* NOTE that sapp_show_mouse() does not "stack" like the Win32 or macOS API functions! */ +SOKOL_API_IMPL void sapp_show_mouse(bool show) { + if (_sapp.mouse.shown != show) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); + #endif + _sapp.mouse.shown = show; + } +} + +SOKOL_API_IMPL bool sapp_mouse_shown(void) { + return _sapp.mouse.shown; +} + +SOKOL_API_IMPL void sapp_lock_mouse(bool lock) { + #if defined(_SAPP_MACOS) + _sapp_macos_lock_mouse(lock); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_lock_mouse(lock); + #elif defined(_SAPP_WIN32) + _sapp_win32_lock_mouse(lock); + #elif defined(_SAPP_LINUX) + _sapp_x11_lock_mouse(lock); + #else + _sapp.mouse.locked = lock; + #endif +} + +SOKOL_API_IMPL bool sapp_mouse_locked(void) { + return _sapp.mouse.locked; +} + +SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.mouse.current_cursor != cursor) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); + #endif + _sapp.mouse.current_cursor = cursor; + } +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { + return _sapp.mouse.current_cursor; +} + +SOKOL_API_IMPL void sapp_request_quit(void) { + _sapp.quit_requested = true; +} + +SOKOL_API_IMPL void sapp_cancel_quit(void) { + _sapp.quit_requested = false; +} + +SOKOL_API_IMPL void sapp_quit(void) { + _sapp.quit_ordered = true; +} + +SOKOL_API_IMPL void sapp_consume_event(void) { + _sapp.event_consumed = true; +} + +/* NOTE: on HTML5, sapp_set_clipboard_string() must be called from within event handler! */ +SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(str); + #if defined(_SAPP_MACOS) + _sapp_macos_set_clipboard_string(str); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_clipboard_string(str); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_clipboard_string(str); + #elif defined(_SAPP_LINUX) + _sapp_x11_set_clipboard_string(str); + #else + /* not implemented */ + #endif + _sapp_strcpy(str, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); +} + +SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { + if (!_sapp.clipboard.enabled) { + return ""; + } + #if defined(_SAPP_MACOS) + return _sapp_macos_get_clipboard_string(); + #elif defined(_SAPP_EMSCRIPTEN) + return _sapp.clipboard.buffer; + #elif defined(_SAPP_WIN32) + return _sapp_win32_get_clipboard_string(); + #elif defined(_SAPP_LINUX) + return _sapp_x11_get_clipboard_string(); + #else + /* not implemented */ + return _sapp.clipboard.buffer; + #endif +} + +SOKOL_API_IMPL void sapp_set_window_title(const char* title) { + SOKOL_ASSERT(title); + _sapp_strcpy(title, _sapp.window_title, sizeof(_sapp.window_title)); + #if defined(_SAPP_MACOS) + _sapp_macos_update_window_title(); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_window_title(); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_window_title(); + #endif +} + +SOKOL_API_IMPL void sapp_set_icon(const sapp_icon_desc* desc) { + SOKOL_ASSERT(desc); + if (desc->sokol_default) { + if (0 == _sapp.default_icon_pixels) { + _sapp_setup_default_icon(); + } + SOKOL_ASSERT(0 != _sapp.default_icon_pixels); + desc = &_sapp.default_icon_desc; + } + const int num_images = _sapp_icon_num_images(desc); + if (num_images == 0) { + return; + } + SOKOL_ASSERT((num_images > 0) && (num_images <= SAPP_MAX_ICONIMAGES)); + if (!_sapp_validate_icon_desc(desc, num_images)) { + return; + } + #if defined(_SAPP_MACOS) + _sapp_macos_set_icon(desc, num_images); + #elif defined(_SAPP_WIN32) + _sapp_win32_set_icon(desc, num_images); + #elif defined(_SAPP_LINUX) + _sapp_x11_set_icon(desc, num_images); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_set_icon(desc, num_images); + #endif +} + +SOKOL_API_IMPL int sapp_get_num_dropped_files(void) { + SOKOL_ASSERT(_sapp.drop.enabled); + return _sapp.drop.num_files; +} + +SOKOL_API_IMPL const char* sapp_get_dropped_file_path(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + SOKOL_ASSERT(_sapp.drop.buffer); + if (!_sapp.drop.enabled) { + return ""; + } + if ((index < 0) || (index >= _sapp.drop.max_files)) { + return ""; + } + return (const char*) _sapp_dropped_file_path_ptr(index); +} + +SOKOL_API_IMPL uint32_t sapp_html5_get_dropped_file_size(int index) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT((index >= 0) && (index < _sapp.drop.num_files)); + #if defined(_SAPP_EMSCRIPTEN) + if (!_sapp.drop.enabled) { + return 0; + } + return sapp_js_dropped_file_size(index); + #else + (void)index; + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_fetch_dropped_file(const sapp_html5_fetch_request* request) { + SOKOL_ASSERT(_sapp.drop.enabled); + SOKOL_ASSERT(request); + SOKOL_ASSERT(request->callback); + SOKOL_ASSERT(request->buffer.ptr); + SOKOL_ASSERT(request->buffer.size > 0); + #if defined(_SAPP_EMSCRIPTEN) + const int index = request->dropped_file_index; + sapp_html5_fetch_error error_code = SAPP_HTML5_FETCH_ERROR_NO_ERROR; + if ((index < 0) || (index >= _sapp.drop.num_files)) { + error_code = SAPP_HTML5_FETCH_ERROR_OTHER; + } + if (sapp_html5_get_dropped_file_size(index) > request->buffer.size) { + error_code = SAPP_HTML5_FETCH_ERROR_BUFFER_TOO_SMALL; + } + if (SAPP_HTML5_FETCH_ERROR_NO_ERROR != error_code) { + _sapp_emsc_invoke_fetch_cb(index, + false, // success + (int)error_code, + request->callback, + 0, // fetched_size + (void*)request->buffer.ptr, + request->buffer.size, + request->user_data); + } + else { + sapp_js_fetch_dropped_file(index, + request->callback, + (void*)request->buffer.ptr, + request->buffer.size, + request->user_data); + } + #else + (void)request; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.mtl_device; + #else + const void* obj = (__bridge const void*) _sapp.ios.mtl_device; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_current_drawable(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view currentDrawable]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view currentDrawable]; + #endif + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_depth_stencil_texture(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view depthStencilTexture]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view depthStencilTexture]; + #endif + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_metal_get_msaa_color_texture(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_METAL) + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) [_sapp.macos.view multisampleColorTexture]; + #else + const void* obj = (__bridge const void*) [_sapp.ios.view multisampleColorTexture]; + #endif + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_macos_get_window(void) { + #if defined(_SAPP_MACOS) + const void* obj = (__bridge const void*) _sapp.macos.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_ios_get_window(void) { + #if defined(_SAPP_IOS) + const void* obj = (__bridge const void*) _sapp.ios.window; + SOKOL_ASSERT(obj); + return obj; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_device_context(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.device_context; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_swap_chain(void) { + SOKOL_ASSERT(_sapp.valid); +#if defined(SOKOL_D3D11) + return _sapp.d3d11.swap_chain; +#else + return 0; +#endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_render_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.d3d11.msaa_rtv); + return _sapp.d3d11.msaa_rtv; + } else { + SOKOL_ASSERT(_sapp.d3d11.rtv); + return _sapp.d3d11.rtv; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_resolve_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.d3d11.rtv); + return _sapp.d3d11.rtv; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_d3d11_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(SOKOL_D3D11) + return _sapp.d3d11.dsv; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_win32_get_hwnd(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_WIN32) + return _sapp.win32.hwnd; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_device(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + return (const void*) _sapp.wgpu.device; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_render_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.wgpu.msaa_view); + return (const void*) _sapp.wgpu.msaa_view; + } else { + SOKOL_ASSERT(_sapp.wgpu.swapchain_view); + return (const void*) _sapp.wgpu.swapchain_view; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_resolve_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + if (_sapp.sample_count > 1) { + SOKOL_ASSERT(_sapp.wgpu.swapchain_view); + return (const void*) _sapp.wgpu.swapchain_view; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_wgpu_get_depth_stencil_view(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_EMSCRIPTEN) && defined(SOKOL_WGPU) + return (const void*) _sapp.wgpu.depth_stencil_view; + #else + return 0; + #endif +} + +SOKOL_API_IMPL uint32_t sapp_gl_get_framebuffer(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANY_GL) + return _sapp.gl.framebuffer; + #else + return 0; + #endif +} + +SOKOL_API_IMPL int sapp_gl_get_major_version(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANY_GL) + return _sapp.desc.gl_major_version; + #else + return 0; + #endif +} + +SOKOL_API_IMPL int sapp_gl_get_minor_version(void) { + SOKOL_ASSERT(_sapp.valid); + #if defined(_SAPP_ANY_GL) + return _sapp.desc.gl_minor_version; + #else + return 0; + #endif +} + +SOKOL_API_IMPL bool sapp_gl_is_gles(void) { + #if defined(SOKOL_GLES3) + return true; + #else + return false; + #endif +} + +SOKOL_API_IMPL const void* sapp_x11_get_window(void) { + #if defined(_SAPP_LINUX) + return (void*)_sapp.x11.window; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_x11_get_display(void) { + #if defined(_SAPP_LINUX) + return (void*)_sapp.x11.display; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { + // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() + // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) + #if defined(_SAPP_ANDROID) + return (void*)_sapp.android.activity; + #else + return 0; + #endif +} + +SOKOL_API_IMPL void sapp_html5_ask_leave_site(bool ask) { + _sapp.html5_ask_leave_site = ask; +} + +#endif /* SOKOL_APP_IMPL */ diff --git a/modules/sokol-jai/sokol/c/sokol_audio.c b/modules/sokol-jai/sokol/c/sokol_audio.c new file mode 100644 index 0000000..53cdb49 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_audio.c @@ -0,0 +1,6 @@ +#if defined(IMPL) +#define SOKOL_AUDIO_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_audio.h" + diff --git a/modules/sokol-jai/sokol/c/sokol_audio.h b/modules/sokol-jai/sokol/c/sokol_audio.h new file mode 100644 index 0000000..cd220ce --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_audio.h @@ -0,0 +1,2310 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL) +#define SOKOL_AUDIO_IMPL +#endif +#ifndef SOKOL_AUDIO_INCLUDED +/* + sokol_audio.h -- cross-platform audio-streaming API + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_AUDIO_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + + SOKOL_DUMMY_BACKEND - use a dummy backend + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024) + SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on + macOS instead of using embedded CoreAudio declarations + + If sokol_audio.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Link with the following libraries: + + - on macOS: AudioToolbox + - on iOS: AudioToolbox, AVFoundation + - on FreeBSD: asound + - on Linux: asound + - on Android: aaudio + - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib + - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32 + + FEATURE OVERVIEW + ================ + You provide a mono- or stereo-stream of 32-bit float samples, which + Sokol Audio feeds into platform-specific audio backends: + + - Windows: WASAPI + - Linux: ALSA + - FreeBSD: ALSA + - macOS: CoreAudio + - iOS: CoreAudio+AVAudioSession + - emscripten: WebAudio with ScriptProcessorNode + - Android: AAudio + + Sokol Audio will not do any buffer mixing or volume control, if you have + multiple independent input streams of sample data you need to perform the + mixing yourself before forwarding the data to Sokol Audio. + + There are two mutually exclusive ways to provide the sample data: + + 1. Callback model: You provide a callback function, which will be called + when Sokol Audio needs new samples. On all platforms except emscripten, + this function is called from a separate thread. + 2. Push model: Your code pushes small blocks of sample data from your + main loop or a thread you created. The pushed data is stored in + a ring buffer where it is pulled by the backend code when + needed. + + The callback model is preferred because it is the most direct way to + feed sample data into the audio backends and also has less moving parts + (there is no ring buffer between your code and the audio backend). + + Sometimes it is not possible to generate the audio stream directly in a + callback function running in a separate thread, for such cases Sokol Audio + provides the push-model as a convenience. + + SOKOL AUDIO, SOLOUD AND MINIAUDIO + ================================= + The WASAPI, ALSA and CoreAudio backend code has been taken from the + SoLoud library (with some modifications, so any bugs in there are most + likely my fault). If you need a more fully-featured audio solution, check + out SoLoud, it's excellent: + + https://github.com/jarikomppa/soloud + + Another alternative which feature-wise is somewhere inbetween SoLoud and + sokol-audio might be MiniAudio: + + https://github.com/mackron/miniaudio + + GLOSSARY + ======== + - stream buffer: + The internal audio data buffer, usually provided by the backend API. The + size of the stream buffer defines the base latency, smaller buffers have + lower latency but may cause audio glitches. Bigger buffers reduce or + eliminate glitches, but have a higher base latency. + + - stream callback: + Optional callback function which is called by Sokol Audio when it + needs new samples. On Windows, macOS/iOS and Linux, this is called in + a separate thread, on WebAudio, this is called per-frame in the + browser thread. + + - channel: + A discrete track of audio data, currently 1-channel (mono) and + 2-channel (stereo) is supported and tested. + + - sample: + The magnitude of an audio signal on one channel at a given time. In + Sokol Audio, samples are 32-bit float numbers in the range -1.0 to + +1.0. + + - frame: + The tightly packed set of samples for all channels at a given time. + For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples. + + - packet: + In Sokol Audio, a small chunk of audio data that is moved from the + main thread to the audio streaming thread in order to decouple the + rate at which the main thread provides new audio data, and the + streaming thread consuming audio data. + + WORKING WITH SOKOL AUDIO + ======================== + First call saudio_setup() with your preferred audio playback options. + In most cases you can stick with the default values, these provide + a good balance between low-latency and glitch-free playback + on all audio backends. + + You should always provide a logging callback to be aware of any + warnings and errors. The easiest way is to use sokol_log.h for this: + + #include "sokol_log.h" + // ... + saudio_setup(&(saudio_desc){ + .logger = { + .func = slog_func, + } + }); + + If you want to use the callback-model, you need to provide a stream + callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb, + otherwise keep both function pointers zero-initialized. + + Use push model and default playback parameters: + + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + Use stream callback model and default playback parameters: + + saudio_setup(&(saudio_desc){ + .stream_cb = my_stream_callback + .logger.func = slog_func, + }); + + The standard stream callback doesn't have a user data argument, if you want + that, use the alternative stream_userdata_cb and also set the user_data pointer: + + saudio_setup(&(saudio_desc){ + .stream_userdata_cb = my_stream_callback, + .user_data = &my_data + .logger.func = slog_func, + }); + + The following playback parameters can be provided through the + saudio_desc struct: + + General parameters (both for stream-callback and push-model): + + int sample_rate -- the sample rate in Hz, default: 44100 + int num_channels -- number of channels, default: 1 (mono) + int buffer_frames -- number of frames in streaming buffer, default: 2048 + + The stream callback prototype (either with or without userdata): + + void (*stream_cb)(float* buffer, int num_frames, int num_channels) + void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data) + Function pointer to the user-provide stream callback. + + Push-model parameters: + + int packet_frames -- number of frames in a packet, default: 128 + int num_packets -- number of packets in ring buffer, default: 64 + + The sample_rate and num_channels parameters are only hints for the audio + backend, it isn't guaranteed that those are the values used for actual + playback. + + To get the actual parameters, call the following functions after + saudio_setup(): + + int saudio_sample_rate(void) + int saudio_channels(void); + + It's unlikely that the number of channels will be different than requested, + but a different sample rate isn't uncommon. + + (NOTE: there's an yet unsolved issue when an audio backend might switch + to a different sample rate when switching output devices, for instance + plugging in a bluetooth headset, this case is currently not handled in + Sokol Audio). + + You can check if audio initialization was successful with + saudio_isvalid(). If backend initialization failed for some reason + (for instance when there's no audio device in the machine), this + will return false. Not checking for success won't do any harm, all + Sokol Audio function will silently fail when called after initialization + has failed, so apart from missing audio output, nothing bad will happen. + + Before your application exits, you should call + + saudio_shutdown(); + + This stops the audio thread (on Linux, Windows and macOS/iOS) and + properly shuts down the audio backend. + + THE STREAM CALLBACK MODEL + ========================= + To use Sokol Audio in stream-callback-mode, provide a callback function + like this in the saudio_desc struct when calling saudio_setup(): + + void stream_cb(float* buffer, int num_frames, int num_channels) { + ... + } + + Or the alternative version with a user-data argument: + + void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) { + my_data_t* my_data = (my_data_t*) user_data; + ... + } + + The job of the callback function is to fill the *buffer* with 32-bit + float sample values. + + To output silence, fill the buffer with zeros: + + void stream_cb(float* buffer, int num_frames, int num_channels) { + const int num_samples = num_frames * num_channels; + for (int i = 0; i < num_samples; i++) { + buffer[i] = 0.0f; + } + } + + For stereo output (num_channels == 2), the samples for the left + and right channel are interleaved: + + void stream_cb(float* buffer, int num_frames, int num_channels) { + assert(2 == num_channels); + for (int i = 0; i < num_frames; i++) { + buffer[2*i + 0] = ...; // left channel + buffer[2*i + 1] = ...; // right channel + } + } + + Please keep in mind that the stream callback function is running in a + separate thread, if you need to share data with the main thread you need + to take care yourself to make the access to the shared data thread-safe! + + THE PUSH MODEL + ============== + To use the push-model for providing audio data, simply don't set (keep + zero-initialized) the stream_cb field in the saudio_desc struct when + calling saudio_setup(). + + To provide sample data with the push model, call the saudio_push() + function at regular intervals (for instance once per frame). You can + call the saudio_expect() function to ask Sokol Audio how much room is + in the ring buffer, but if you provide a continuous stream of data + at the right sample rate, saudio_expect() isn't required (it's a simple + way to sync/throttle your sample generation code with the playback + rate though). + + With saudio_push() you may need to maintain your own intermediate sample + buffer, since pushing individual sample values isn't very efficient. + The following example is from the MOD player sample in + sokol-samples (https://github.com/floooh/sokol-samples): + + const int num_frames = saudio_expect(); + if (num_frames > 0) { + const int num_samples = num_frames * saudio_channels(); + read_samples(flt_buf, num_samples); + saudio_push(flt_buf, num_frames); + } + + Another option is to ignore saudio_expect(), and just push samples as they + are generated in small batches. In this case you *need* to generate the + samples at the right sample rate: + + The following example is taken from the Tiny Emulators project + (https://github.com/floooh/chips-test), this is for mono playback, + so (num_samples == num_frames): + + // tick the sound generator + if (ay38910_tick(&sys->psg)) { + // new sample is ready + sys->sample_buffer[sys->sample_pos++] = sys->psg.sample; + if (sys->sample_pos == sys->num_samples) { + // new sample packet is ready + saudio_push(sys->sample_buffer, sys->num_samples); + sys->sample_pos = 0; + } + } + + THE WEBAUDIO BACKEND + ==================== + The WebAudio backend is currently using a ScriptProcessorNode callback to + feed the sample data into WebAudio. ScriptProcessorNode has been + deprecated for a while because it is running from the main thread, with + the default initialization parameters it works 'pretty well' though. + Ultimately Sokol Audio will use Audio Worklets, but this requires a few + more things to fall into place (Audio Worklets implemented everywhere, + SharedArrayBuffers enabled again, and I need to figure out a 'low-cost' + solution in terms of implementation effort, since Audio Worklets are + a lot more complex than ScriptProcessorNode if the audio data needs to come + from the main thread). + + The WebAudio backend is automatically selected when compiling for + emscripten (__EMSCRIPTEN__ define exists). + + https://developers.google.com/web/updates/2017/12/audio-worklet + https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern + + "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/ + + Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/ + + THE COREAUDIO BACKEND + ===================== + The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined). + Since the CoreAudio API is implemented in C (not Objective-C) on macOS the + implementation part of Sokol Audio can be included into a C source file. + + However on iOS, Sokol Audio must be compiled as Objective-C due to it's + reliance on the AVAudioSession object. The iOS code path support both + being compiled with or without ARC (Automatic Reference Counting). + + For thread synchronisation, the CoreAudio backend will use the + pthread_mutex_* functions. + + The incoming floating point samples will be directly forwarded to + CoreAudio without further conversion. + + macOS and iOS applications that use Sokol Audio need to link with + the AudioToolbox framework. + + THE WASAPI BACKEND + ================== + The WASAPI backend is automatically selected when compiling on Windows + (_WIN32 is defined). + + For thread synchronisation a Win32 critical section is used. + + WASAPI may use a different size for its own streaming buffer then requested, + so the base latency may be slightly bigger. The current backend implementation + converts the incoming floating point sample values to signed 16-bit + integers. + + The required Windows system DLLs are linked with #pragma comment(lib, ...), + so you shouldn't need to add additional linker libs in the build process + (otherwise this is a bug which should be fixed in sokol_audio.h). + + THE ALSA BACKEND + ================ + The ALSA backend is automatically selected when compiling on Linux + ('linux' is defined). + + For thread synchronisation, the pthread_mutex_* functions are used. + + Samples are directly forwarded to ALSA in 32-bit float format, no + further conversion is taking place. + + You need to link with the 'asound' library, and the + header must be present (usually both are installed with some sort + of ALSA development package). + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + saudio_setup(&(saudio_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_audio.h + itself though, not any allocations in OS libraries. + + Memory allocation will only happen on the same thread where saudio_setup() + was called, so you don't need to worry about thread-safety. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'saudio' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SAUDIO_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_audio.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-audio like this: + + saudio_setup(&(saudio_desc){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_AUDIO_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL) +#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_AUDIO_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL) +#define SOKOL_AUDIO_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_AUDIO_API_DECL __declspec(dllimport) +#else +#define SOKOL_AUDIO_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + saudio_log_item + + Log items are defined via X-Macros, and expanded to an + enum 'saudio_log_item', and in debug mode only, + corresponding strings. + + Used as parameter in the logging callback. +*/ +#define _SAUDIO_LOG_ITEMS \ + _SAUDIO_LOGITEM_XMACRO(OK, "Ok") \ + _SAUDIO_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_OPEN_FAILED, "snd_pcm_open() failed") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_FLOAT_SAMPLES_NOT_SUPPORTED, "floating point sample format not supported") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED, "requested buffer size not supported") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED, "requested channel count not supported") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED, "snd_pcm_hw_params_set_rate_near() failed") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_HW_PARAMS_FAILED, "snd_pcm_hw_params() failed") \ + _SAUDIO_LOGITEM_XMACRO(ALSA_PTHREAD_CREATE_FAILED, "pthread_create() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_EVENT_FAILED, "CreateEvent() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED, "CoCreateInstance() for IMMDeviceEnumerator failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED, "IMMDeviceEnumerator.GetDefaultAudioEndpoint() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_DEVICE_ACTIVATE_FAILED, "IMMDevice.Activate() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED, "IAudioClient.Initialize() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED, "IAudioClient.GetBufferSize() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED, "IAudioClient.GetService() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED, "IAudioClient.SetEventHandle() failed") \ + _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_THREAD_FAILED, "CreateThread() failed") \ + _SAUDIO_LOGITEM_XMACRO(AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED, "AAudioStreamBuilder_openStream() failed") \ + _SAUDIO_LOGITEM_XMACRO(AAUDIO_PTHREAD_CREATE_FAILED, "pthread_create() failed after AAUDIO_ERROR_DISCONNECTED") \ + _SAUDIO_LOGITEM_XMACRO(AAUDIO_RESTARTING_STREAM_AFTER_ERROR, "restarting AAudio stream after error") \ + _SAUDIO_LOGITEM_XMACRO(USING_AAUDIO_BACKEND, "using AAudio backend") \ + _SAUDIO_LOGITEM_XMACRO(AAUDIO_CREATE_STREAMBUILDER_FAILED, "AAudio_createStreamBuilder() failed") \ + _SAUDIO_LOGITEM_XMACRO(COREAUDIO_NEW_OUTPUT_FAILED, "AudioQueueNewOutput() failed") \ + _SAUDIO_LOGITEM_XMACRO(COREAUDIO_ALLOCATE_BUFFER_FAILED, "AudioQueueAllocateBuffer() failed") \ + _SAUDIO_LOGITEM_XMACRO(COREAUDIO_START_FAILED, "AudioQueueStart() failed") \ + _SAUDIO_LOGITEM_XMACRO(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE, "backend buffer size isn't multiple of packet size") \ + +#define _SAUDIO_LOGITEM_XMACRO(item,msg) SAUDIO_LOGITEM_##item, +typedef enum saudio_log_item { + _SAUDIO_LOG_ITEMS +} saudio_log_item; +#undef _SAUDIO_LOGITEM_XMACRO + +/* + saudio_logger + + Used in saudio_desc to provide a custom logging and error reporting + callback to sokol-audio. +*/ +typedef struct saudio_logger { + void (*func)( + const char* tag, // always "saudio" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SAUDIO_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_audio.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} saudio_logger; + +/* + saudio_allocator + + Used in saudio_desc to provide custom memory-alloc and -free functions + to sokol_audio.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct saudio_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} saudio_allocator; + +typedef struct saudio_desc { + int sample_rate; // requested sample rate + int num_channels; // number of channels, default: 1 (mono) + int buffer_frames; // number of frames in streaming buffer + int packet_frames; // number of frames in a packet + int num_packets; // number of packets in packet queue + void (*stream_cb)(float* buffer, int num_frames, int num_channels); // optional streaming callback (no user data) + void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); //... and with user data + void* user_data; // optional user data argument for stream_userdata_cb + saudio_allocator allocator; // optional allocation override functions + saudio_logger logger; // optional logging function (default: NO LOGGING!) +} saudio_desc; + +/* setup sokol-audio */ +SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc); +/* shutdown sokol-audio */ +SOKOL_AUDIO_API_DECL void saudio_shutdown(void); +/* true after setup if audio backend was successfully initialized */ +SOKOL_AUDIO_API_DECL bool saudio_isvalid(void); +/* return the saudio_desc.user_data pointer */ +SOKOL_AUDIO_API_DECL void* saudio_userdata(void); +/* return a copy of the original saudio_desc struct */ +SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void); +/* actual sample rate */ +SOKOL_AUDIO_API_DECL int saudio_sample_rate(void); +/* return actual backend buffer size in number of frames */ +SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void); +/* actual number of channels */ +SOKOL_AUDIO_API_DECL int saudio_channels(void); +/* return true if audio context is currently suspended (only in WebAudio backend, all other backends return false) */ +SOKOL_AUDIO_API_DECL bool saudio_suspended(void); +/* get current number of frames to fill packet queue */ +SOKOL_AUDIO_API_DECL int saudio_expect(void); +/* push sample frames from main thread, returns number of frames actually pushed */ +SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for c++ */ +inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); } + +#endif +#endif // SOKOL_AUDIO_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_AUDIO_IMPL +#define SOKOL_AUDIO_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use saudio_desc.allocator to override memory allocation functions" +#endif + +#include // alloc, free +#include // memset, memcpy +#include // size_t + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +// platform detection defines +#if defined(SOKOL_DUMMY_BACKEND) + // nothing +#elif defined(__APPLE__) + #define _SAUDIO_APPLE (1) + #include + #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE + #define _SAUDIO_IOS (1) + #else + #define _SAUDIO_MACOS (1) + #endif +#elif defined(__EMSCRIPTEN__) + #define _SAUDIO_EMSCRIPTEN (1) +#elif defined(_WIN32) + #define _SAUDIO_WINDOWS (1) + #include + #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)) + #error "sokol_audio.h no longer supports UWP" + #endif +#elif defined(__ANDROID__) + #define _SAUDIO_ANDROID (1) +#elif defined(__linux__) || defined(__unix__) + #define _SAUDIO_LINUX (1) +#else +#error "sokol_audio.h: Unknown platform" +#endif + +// platform-specific headers and definitions +#if defined(SOKOL_DUMMY_BACKEND) + #define _SAUDIO_NOTHREADS (1) +#elif defined(_SAUDIO_WINDOWS) + #define _SAUDIO_WINTHREADS (1) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #pragma comment (lib, "kernel32") + #pragma comment (lib, "ole32") + #ifndef CINTERFACE + #define CINTERFACE + #endif + #ifndef COBJMACROS + #define COBJMACROS + #endif + #ifndef CONST_VTABLE + #define CONST_VTABLE + #endif + #include + #include + static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} }; + static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} }; + static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} }; + static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} }; + static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2} }; + static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} }; + static const GUID _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} }; + #if defined(__cplusplus) + #define _SOKOL_AUDIO_WIN32COM_ID(x) (x) + #else + #define _SOKOL_AUDIO_WIN32COM_ID(x) (&x) + #endif + /* fix for Visual Studio 2015 SDKs */ + #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM + #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 + #endif + #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY + #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 + #endif + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4505) /* unreferenced local function has been removed */ + #endif +#elif defined(_SAUDIO_APPLE) + #define _SAUDIO_PTHREADS (1) + #include + #if defined(_SAUDIO_IOS) + // always use system headers on iOS (for now at least) + #if !defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) + #define SAUDIO_OSX_USE_SYSTEM_HEADERS (1) + #endif + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #include + #include + #else + #if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) + #include + #endif + #endif +#elif defined(_SAUDIO_ANDROID) + #define _SAUDIO_PTHREADS (1) + #include + #include "aaudio/AAudio.h" +#elif defined(_SAUDIO_LINUX) + #if !defined(__FreeBSD__) + #include + #endif + #define _SAUDIO_PTHREADS (1) + #include + #define ALSA_PCM_NEW_HW_PARAMS_API + #include +#elif defined(__EMSCRIPTEN__) + #define _SAUDIO_NOTHREADS (1) + #include +#endif + +#define _saudio_def(val, def) (((val) == 0) ? (def) : (val)) +#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) + +#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100) +#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048) +#define _SAUDIO_DEFAULT_PACKET_FRAMES (128) +#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4) + +#ifndef SAUDIO_RING_MAX_SLOTS +#define SAUDIO_RING_MAX_SLOTS (1024) +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs +#if defined(_SAUDIO_PTHREADS) + +typedef struct { + pthread_mutex_t mutex; +} _saudio_mutex_t; + +#elif defined(_SAUDIO_WINTHREADS) + +typedef struct { + CRITICAL_SECTION critsec; +} _saudio_mutex_t; + +#elif defined(_SAUDIO_NOTHREADS) + +typedef struct { + int dummy_mutex; +} _saudio_mutex_t; + +#endif + +#if defined(SOKOL_DUMMY_BACKEND) + +typedef struct { + int dummy; +} _saudio_dummy_backend_t; + +#elif defined(_SAUDIO_APPLE) + +#if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS) + +typedef AudioQueueRef _saudio_AudioQueueRef; +typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef; +typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription; +typedef OSStatus _saudio_OSStatus; + +#define _saudio_kAudioFormatLinearPCM (kAudioFormatLinearPCM) +#define _saudio_kLinearPCMFormatFlagIsFloat (kLinearPCMFormatFlagIsFloat) +#define _saudio_kAudioFormatFlagIsPacked (kAudioFormatFlagIsPacked) + +#else +#ifdef __cplusplus +extern "C" { +#endif + +// embedded AudioToolbox declarations +typedef uint32_t _saudio_AudioFormatID; +typedef uint32_t _saudio_AudioFormatFlags; +typedef int32_t _saudio_OSStatus; +typedef uint32_t _saudio_SMPTETimeType; +typedef uint32_t _saudio_SMPTETimeFlags; +typedef uint32_t _saudio_AudioTimeStampFlags; +typedef void* _saudio_CFRunLoopRef; +typedef void* _saudio_CFStringRef; +typedef void* _saudio_AudioQueueRef; + +#define _saudio_kAudioFormatLinearPCM ('lpcm') +#define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0) +#define _saudio_kAudioFormatFlagIsPacked (1U << 3) + +typedef struct _saudio_AudioStreamBasicDescription { + double mSampleRate; + _saudio_AudioFormatID mFormatID; + _saudio_AudioFormatFlags mFormatFlags; + uint32_t mBytesPerPacket; + uint32_t mFramesPerPacket; + uint32_t mBytesPerFrame; + uint32_t mChannelsPerFrame; + uint32_t mBitsPerChannel; + uint32_t mReserved; +} _saudio_AudioStreamBasicDescription; + +typedef struct _saudio_AudioStreamPacketDescription { + int64_t mStartOffset; + uint32_t mVariableFramesInPacket; + uint32_t mDataByteSize; +} _saudio_AudioStreamPacketDescription; + +typedef struct _saudio_SMPTETime { + int16_t mSubframes; + int16_t mSubframeDivisor; + uint32_t mCounter; + _saudio_SMPTETimeType mType; + _saudio_SMPTETimeFlags mFlags; + int16_t mHours; + int16_t mMinutes; + int16_t mSeconds; + int16_t mFrames; +} _saudio_SMPTETime; + +typedef struct _saudio_AudioTimeStamp { + double mSampleTime; + uint64_t mHostTime; + double mRateScalar; + uint64_t mWordClockTime; + _saudio_SMPTETime mSMPTETime; + _saudio_AudioTimeStampFlags mFlags; + uint32_t mReserved; +} _saudio_AudioTimeStamp; + +typedef struct _saudio_AudioQueueBuffer { + const uint32_t mAudioDataBytesCapacity; + void* const mAudioData; + uint32_t mAudioDataByteSize; + void * mUserData; + const uint32_t mPacketDescriptionCapacity; + _saudio_AudioStreamPacketDescription* const mPacketDescriptions; + uint32_t mPacketDescriptionCount; +} _saudio_AudioQueueBuffer; +typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef; + +typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer); + +extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ); +extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate); +extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer); +extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs); +extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime); +extern _saudio_OSStatus AudioQueueStop(_saudio_AudioQueueRef inAQ, bool inImmediate); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SAUDIO_OSX_USE_SYSTEM_HEADERS + +typedef struct { + _saudio_AudioQueueRef ca_audio_queue; + #if defined(_SAUDIO_IOS) + id ca_interruption_handler; + #endif +} _saudio_apple_backend_t; + +#elif defined(_SAUDIO_LINUX) + +typedef struct { + snd_pcm_t* device; + float* buffer; + int buffer_byte_size; + int buffer_frames; + pthread_t thread; + bool thread_stop; +} _saudio_alsa_backend_t; + +#elif defined(_SAUDIO_ANDROID) + +typedef struct { + AAudioStreamBuilder* builder; + AAudioStream* stream; + pthread_t thread; + pthread_mutex_t mutex; +} _saudio_aaudio_backend_t; + +#elif defined(_SAUDIO_WINDOWS) + +typedef struct { + HANDLE thread_handle; + HANDLE buffer_end_event; + bool stop; + UINT32 dst_buffer_frames; + int src_buffer_frames; + int src_buffer_byte_size; + int src_buffer_pos; + float* src_buffer; +} _saudio_wasapi_thread_data_t; + +typedef struct { + IMMDeviceEnumerator* device_enumerator; + IMMDevice* device; + IAudioClient* audio_client; + IAudioRenderClient* render_client; + _saudio_wasapi_thread_data_t thread; +} _saudio_wasapi_backend_t; + +#elif defined(_SAUDIO_EMSCRIPTEN) + +typedef struct { + uint8_t* buffer; +} _saudio_web_backend_t; + +#else +#error "unknown platform" +#endif + +#if defined(SOKOL_DUMMY_BACKEND) +typedef _saudio_dummy_backend_t _saudio_backend_t; +#elif defined(_SAUDIO_APPLE) +typedef _saudio_apple_backend_t _saudio_backend_t; +#elif defined(_SAUDIO_EMSCRIPTEN) +typedef _saudio_web_backend_t _saudio_backend_t; +#elif defined(_SAUDIO_WINDOWS) +typedef _saudio_wasapi_backend_t _saudio_backend_t; +#elif defined(_SAUDIO_ANDROID) +typedef _saudio_aaudio_backend_t _saudio_backend_t; +#elif defined(_SAUDIO_LINUX) +typedef _saudio_alsa_backend_t _saudio_backend_t; +#endif + +/* a ringbuffer structure */ +typedef struct { + int head; // next slot to write to + int tail; // next slot to read from + int num; // number of slots in queue + int queue[SAUDIO_RING_MAX_SLOTS]; +} _saudio_ring_t; + +/* a packet FIFO structure */ +typedef struct { + bool valid; + int packet_size; /* size of a single packets in bytes(!) */ + int num_packets; /* number of packet in fifo */ + uint8_t* base_ptr; /* packet memory chunk base pointer (dynamically allocated) */ + int cur_packet; /* current write-packet */ + int cur_offset; /* current byte-offset into current write packet */ + _saudio_mutex_t mutex; /* mutex for thread-safe access */ + _saudio_ring_t read_queue; /* buffers with data, ready to be streamed */ + _saudio_ring_t write_queue; /* empty buffers, ready to be pushed to */ +} _saudio_fifo_t; + +/* sokol-audio state */ +typedef struct { + bool valid; + bool setup_called; + void (*stream_cb)(float* buffer, int num_frames, int num_channels); + void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); + void* user_data; + int sample_rate; /* sample rate */ + int buffer_frames; /* number of frames in streaming buffer */ + int bytes_per_frame; /* filled by backend */ + int packet_frames; /* number of frames in a packet */ + int num_packets; /* number of packets in packet queue */ + int num_channels; /* actual number of channels */ + saudio_desc desc; + _saudio_fifo_t fifo; + _saudio_backend_t backend; +} _saudio_state_t; + +_SOKOL_PRIVATE _saudio_state_t _saudio; + +_SOKOL_PRIVATE bool _saudio_has_callback(void) { + return (_saudio.stream_cb || _saudio.stream_userdata_cb); +} + +_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) { + if (_saudio.stream_cb) { + _saudio.stream_cb(buffer, num_frames, num_channels); + } + else if (_saudio.stream_userdata_cb) { + _saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data); + } +} + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SAUDIO_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _saudio_log_messages[] = { + _SAUDIO_LOG_ITEMS +}; +#undef _SAUDIO_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SAUDIO_PANIC(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 0, __LINE__) +#define _SAUDIO_ERROR(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 1, __LINE__) +#define _SAUDIO_WARN(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 2, __LINE__) +#define _SAUDIO_INFO(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 3, __LINE__) + +static void _saudio_log(saudio_log_item log_item, uint32_t log_level, uint32_t line_nr) { + if (_saudio.desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _saudio_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _saudio.desc.logger.func("saudio", log_level, (uint32_t)log_item, message, line_nr, filename, _saudio.desc.logger.user_data); + } + else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _saudio_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _saudio_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_saudio.desc.allocator.alloc_fn) { + ptr = _saudio.desc.allocator.alloc_fn(size, _saudio.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SAUDIO_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _saudio_malloc_clear(size_t size) { + void* ptr = _saudio_malloc(size); + _saudio_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _saudio_free(void* ptr) { + if (_saudio.desc.allocator.free_fn) { + _saudio.desc.allocator.free_fn(ptr, _saudio.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ███ ███ ██ ██ ████████ ███████ ██ ██ +// ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ ██ ██ ██ █████ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██████ ██ ███████ ██ ██ +// +// >>mutex +#if defined(_SAUDIO_NOTHREADS) + +_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; } +_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; } +_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; } +_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; } + +#elif defined(_SAUDIO_PTHREADS) + +_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init(&m->mutex, &attr); +} + +_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { + pthread_mutex_destroy(&m->mutex); +} + +_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { + pthread_mutex_lock(&m->mutex); +} + +_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { + pthread_mutex_unlock(&m->mutex); +} + +#elif defined(_SAUDIO_WINTHREADS) + +_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { + InitializeCriticalSection(&m->critsec); +} + +_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { + DeleteCriticalSection(&m->critsec); +} + +_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { + EnterCriticalSection(&m->critsec); +} + +_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { + LeaveCriticalSection(&m->critsec); +} +#else +#error "sokol_audio.h: unknown platform!" +#endif + +// ██████ ██ ███ ██ ██████ ██████ ██ ██ ███████ ███████ ███████ ██████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ ███ ██████ ██ ██ █████ █████ █████ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ████ ██████ ██████ ██████ ██ ██ ███████ ██ ██ +// +// >>ringbuffer +_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) { + return (i % ring->num); +} + +_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) { + SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS); + ring->head = 0; + ring->tail = 0; + /* one slot reserved to detect 'full' vs 'empty' */ + ring->num = num_slots + 1; +} + +_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) { + return _saudio_ring_idx(ring, ring->head + 1) == ring->tail; +} + +_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) { + return ring->head == ring->tail; +} + +_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) { + int count; + if (ring->head >= ring->tail) { + count = ring->head - ring->tail; + } + else { + count = (ring->head + ring->num) - ring->tail; + } + SOKOL_ASSERT(count < ring->num); + return count; +} + +_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) { + SOKOL_ASSERT(!_saudio_ring_full(ring)); + ring->queue[ring->head] = val; + ring->head = _saudio_ring_idx(ring, ring->head + 1); +} + +_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) { + SOKOL_ASSERT(!_saudio_ring_empty(ring)); + int val = ring->queue[ring->tail]; + ring->tail = _saudio_ring_idx(ring, ring->tail + 1); + return val; +} + +// ███████ ██ ███████ ██████ +// ██ ██ ██ ██ ██ +// █████ ██ █████ ██ ██ +// ██ ██ ██ ██ ██ +// ██ ██ ██ ██████ +// +// >>fifo +_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) { + /* this must be called before initializing both the backend and the fifo itself! */ + _saudio_mutex_init(&fifo->mutex); +} + +_SOKOL_PRIVATE void _saudio_fifo_destroy_mutex(_saudio_fifo_t* fifo) { + _saudio_mutex_destroy(&fifo->mutex); +} + +_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) { + /* NOTE: there's a chicken-egg situation during the init phase where the + streaming thread must be started before the fifo is actually initialized, + thus the fifo init must already be protected from access by the fifo_read() func. + */ + _saudio_mutex_lock(&fifo->mutex); + SOKOL_ASSERT((packet_size > 0) && (num_packets > 0)); + fifo->packet_size = packet_size; + fifo->num_packets = num_packets; + fifo->base_ptr = (uint8_t*) _saudio_malloc((size_t)(packet_size * num_packets)); + fifo->cur_packet = -1; + fifo->cur_offset = 0; + _saudio_ring_init(&fifo->read_queue, num_packets); + _saudio_ring_init(&fifo->write_queue, num_packets); + for (int i = 0; i < num_packets; i++) { + _saudio_ring_enqueue(&fifo->write_queue, i); + } + SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue)); + SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets); + SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue)); + SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0); + fifo->valid = true; + _saudio_mutex_unlock(&fifo->mutex); +} + +_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) { + SOKOL_ASSERT(fifo->base_ptr); + _saudio_free(fifo->base_ptr); + fifo->base_ptr = 0; + fifo->valid = false; +} + +_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) { + _saudio_mutex_lock(&fifo->mutex); + int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size); + if (fifo->cur_packet != -1) { + num_bytes += fifo->packet_size - fifo->cur_offset; + } + _saudio_mutex_unlock(&fifo->mutex); + SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size))); + return num_bytes; +} + +/* write new data to the write queue, this is called from main thread */ +_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) { + /* returns the number of bytes written, this will be smaller then requested + if the write queue runs full + */ + int all_to_copy = num_bytes; + while (all_to_copy > 0) { + /* need to grab a new packet? */ + if (fifo->cur_packet == -1) { + _saudio_mutex_lock(&fifo->mutex); + if (!_saudio_ring_empty(&fifo->write_queue)) { + fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue); + } + _saudio_mutex_unlock(&fifo->mutex); + SOKOL_ASSERT(fifo->cur_offset == 0); + } + /* append data to current write packet */ + if (fifo->cur_packet != -1) { + int to_copy = all_to_copy; + const int max_copy = fifo->packet_size - fifo->cur_offset; + if (to_copy > max_copy) { + to_copy = max_copy; + } + uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset; + memcpy(dst, ptr, (size_t)to_copy); + ptr += to_copy; + fifo->cur_offset += to_copy; + all_to_copy -= to_copy; + SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size); + SOKOL_ASSERT(all_to_copy >= 0); + } + else { + /* early out if we're starving */ + int bytes_copied = num_bytes - all_to_copy; + SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes)); + return bytes_copied; + } + /* if write packet is full, push to read queue */ + if (fifo->cur_offset == fifo->packet_size) { + _saudio_mutex_lock(&fifo->mutex); + _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet); + _saudio_mutex_unlock(&fifo->mutex); + fifo->cur_packet = -1; + fifo->cur_offset = 0; + } + } + SOKOL_ASSERT(all_to_copy == 0); + return num_bytes; +} + +/* read queued data, this is called form the stream callback (maybe separate thread) */ +_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) { + /* NOTE: fifo_read might be called before the fifo is properly initialized */ + _saudio_mutex_lock(&fifo->mutex); + int num_bytes_copied = 0; + if (fifo->valid) { + SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size)); + SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets)); + const int num_packets_needed = num_bytes / fifo->packet_size; + uint8_t* dst = ptr; + /* either pull a full buffer worth of data, or nothing */ + if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) { + for (int i = 0; i < num_packets_needed; i++) { + int packet_index = _saudio_ring_dequeue(&fifo->read_queue); + _saudio_ring_enqueue(&fifo->write_queue, packet_index); + const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size; + memcpy(dst, src, (size_t)fifo->packet_size); + dst += fifo->packet_size; + num_bytes_copied += fifo->packet_size; + } + SOKOL_ASSERT(num_bytes == num_bytes_copied); + } + } + _saudio_mutex_unlock(&fifo->mutex); + return num_bytes_copied; +} + +// ██████ ██ ██ ███ ███ ███ ███ ██ ██ +// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ +// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ██ ██ ██ ██ +// +// >>dummy +#if defined(SOKOL_DUMMY_BACKEND) +_SOKOL_PRIVATE bool _saudio_dummy_backend_init(void) { + _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); + return true; +} +_SOKOL_PRIVATE void _saudio_dummy_backend_shutdown(void) { } + +// █████ ██ ███████ █████ +// ██ ██ ██ ██ ██ ██ +// ███████ ██ ███████ ███████ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ██ +// +// >>alsa +#elif defined(_SAUDIO_LINUX) + +/* the streaming callback runs in a separate thread */ +_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) { + _SOKOL_UNUSED(param); + while (!_saudio.backend.thread_stop) { + /* snd_pcm_writei() will be blocking until it needs data */ + int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames); + if (write_res < 0) { + /* underrun occurred */ + snd_pcm_prepare(_saudio.backend.device); + } + else { + /* fill the streaming buffer with new data */ + if (_saudio_has_callback()) { + _saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels); + } + else { + if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) { + /* not enough read data available, fill the entire buffer with silence */ + _saudio_clear(_saudio.backend.buffer, (size_t)_saudio.backend.buffer_byte_size); + } + } + } + } + return 0; +} + +_SOKOL_PRIVATE bool _saudio_alsa_backend_init(void) { + int dir; uint32_t rate; + int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (rc < 0) { + _SAUDIO_ERROR(ALSA_SND_PCM_OPEN_FAILED); + return false; + } + + /* configuration works by restricting the 'configuration space' step + by step, we require all parameters except the sample rate to + match perfectly + */ + snd_pcm_hw_params_t* params = 0; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(_saudio.backend.device, params); + snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) { + _SAUDIO_ERROR(ALSA_FLOAT_SAMPLES_NOT_SUPPORTED); + goto error; + } + if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) { + _SAUDIO_ERROR(ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED); + goto error; + } + if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) { + _SAUDIO_ERROR(ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED); + goto error; + } + /* let ALSA pick a nearby sampling rate */ + rate = (uint32_t) _saudio.sample_rate; + dir = 0; + if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) { + _SAUDIO_ERROR(ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED); + goto error; + } + if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) { + _SAUDIO_ERROR(ALSA_SND_PCM_HW_PARAMS_FAILED); + goto error; + } + + /* read back actual sample rate and channels */ + _saudio.sample_rate = (int)rate; + _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); + + /* allocate the streaming buffer */ + _saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame; + _saudio.backend.buffer_frames = _saudio.buffer_frames; + _saudio.backend.buffer = (float*) _saudio_malloc_clear((size_t)_saudio.backend.buffer_byte_size); + + /* create the buffer-streaming start thread */ + if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) { + _SAUDIO_ERROR(ALSA_PTHREAD_CREATE_FAILED); + goto error; + } + + return true; +error: + if (_saudio.backend.device) { + snd_pcm_close(_saudio.backend.device); + _saudio.backend.device = 0; + } + return false; +}; + +_SOKOL_PRIVATE void _saudio_alsa_backend_shutdown(void) { + SOKOL_ASSERT(_saudio.backend.device); + _saudio.backend.thread_stop = true; + pthread_join(_saudio.backend.thread, 0); + snd_pcm_drain(_saudio.backend.device); + snd_pcm_close(_saudio.backend.device); + _saudio_free(_saudio.backend.buffer); +}; + +// ██ ██ █████ ███████ █████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ███████ ███████ ███████ ██████ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ +// ███ ███ ██ ██ ███████ ██ ██ ██ ██ +// +// >>wasapi +#elif defined(_SAUDIO_WINDOWS) + +/* fill intermediate buffer with new data and reset buffer_pos */ +_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) { + if (_saudio_has_callback()) { + _saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels); + } + else { + if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) { + /* not enough read data available, fill the entire buffer with silence */ + _saudio_clear(_saudio.backend.thread.src_buffer, (size_t)_saudio.backend.thread.src_buffer_byte_size); + } + } +} + +_SOKOL_PRIVATE int _saudio_wasapi_min(int a, int b) { + return (a < b) ? a : b; +} + +_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) { + BYTE* wasapi_buffer = 0; + if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) { + return; + } + SOKOL_ASSERT(wasapi_buffer); + + /* copy samples to WASAPI buffer, refill source buffer if needed */ + int num_remaining_samples = num_frames * _saudio.num_channels; + int buffer_pos = _saudio.backend.thread.src_buffer_pos; + const int buffer_size_in_samples = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float); + float* dst = (float*)wasapi_buffer; + const float* dst_end = dst + num_remaining_samples; + _SOKOL_UNUSED(dst_end); // suppress unused warning in release mode + const float* src = _saudio.backend.thread.src_buffer; + + while (num_remaining_samples > 0) { + if (0 == buffer_pos) { + _saudio_wasapi_fill_buffer(); + } + const int samples_to_copy = _saudio_wasapi_min(num_remaining_samples, buffer_size_in_samples - buffer_pos); + SOKOL_ASSERT((buffer_pos + samples_to_copy) <= buffer_size_in_samples); + SOKOL_ASSERT((dst + samples_to_copy) <= dst_end); + memcpy(dst, &src[buffer_pos], (size_t)samples_to_copy * sizeof(float)); + num_remaining_samples -= samples_to_copy; + SOKOL_ASSERT(num_remaining_samples >= 0); + buffer_pos += samples_to_copy; + dst += samples_to_copy; + + SOKOL_ASSERT(buffer_pos <= buffer_size_in_samples); + if (buffer_pos == buffer_size_in_samples) { + buffer_pos = 0; + } + } + _saudio.backend.thread.src_buffer_pos = buffer_pos; + IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0); +} + +_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) { + (void)param; + _saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames); + IAudioClient_Start(_saudio.backend.audio_client); + while (!_saudio.backend.thread.stop) { + WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE); + UINT32 padding = 0; + if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) { + continue; + } + SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding); + int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding; + if (num_frames > 0) { + _saudio_wasapi_submit_buffer(num_frames); + } + } + return 0; +} + +_SOKOL_PRIVATE void _saudio_wasapi_release(void) { + if (_saudio.backend.thread.src_buffer) { + _saudio_free(_saudio.backend.thread.src_buffer); + _saudio.backend.thread.src_buffer = 0; + } + if (_saudio.backend.render_client) { + IAudioRenderClient_Release(_saudio.backend.render_client); + _saudio.backend.render_client = 0; + } + if (_saudio.backend.audio_client) { + IAudioClient_Release(_saudio.backend.audio_client); + _saudio.backend.audio_client = 0; + } + if (_saudio.backend.device) { + IMMDevice_Release(_saudio.backend.device); + _saudio.backend.device = 0; + } + if (_saudio.backend.device_enumerator) { + IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator); + _saudio.backend.device_enumerator = 0; + } + if (0 != _saudio.backend.thread.buffer_end_event) { + CloseHandle(_saudio.backend.thread.buffer_end_event); + _saudio.backend.thread.buffer_end_event = 0; + } +} + +_SOKOL_PRIVATE bool _saudio_wasapi_backend_init(void) { + REFERENCE_TIME dur; + /* CoInitializeEx could have been called elsewhere already, in which + case the function returns with S_FALSE (thus it does not make much + sense to check the result) + */ + HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); + _SOKOL_UNUSED(hr); + _saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0); + if (0 == _saudio.backend.thread.buffer_end_event) { + _SAUDIO_ERROR(WASAPI_CREATE_EVENT_FAILED); + goto error; + } + if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator), + 0, CLSCTX_ALL, + _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator), + (void**)&_saudio.backend.device_enumerator))) + { + _SAUDIO_ERROR(WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED); + goto error; + } + if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator, + eRender, eConsole, + &_saudio.backend.device))) + { + _SAUDIO_ERROR(WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED); + goto error; + } + if (FAILED(IMMDevice_Activate(_saudio.backend.device, + _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), + CLSCTX_ALL, 0, + (void**)&_saudio.backend.audio_client))) + { + _SAUDIO_ERROR(WASAPI_DEVICE_ACTIVATE_FAILED); + goto error; + } + + WAVEFORMATEXTENSIBLE fmtex; + _saudio_clear(&fmtex, sizeof(fmtex)); + fmtex.Format.nChannels = (WORD)_saudio.num_channels; + fmtex.Format.nSamplesPerSec = (DWORD)_saudio.sample_rate; + fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + fmtex.Format.wBitsPerSample = 32; + fmtex.Format.nBlockAlign = (fmtex.Format.nChannels * fmtex.Format.wBitsPerSample) / 8; + fmtex.Format.nAvgBytesPerSec = fmtex.Format.nSamplesPerSec * fmtex.Format.nBlockAlign; + fmtex.Format.cbSize = 22; /* WORD + DWORD + GUID */ + fmtex.Samples.wValidBitsPerSample = 32; + if (_saudio.num_channels == 1) { + fmtex.dwChannelMask = SPEAKER_FRONT_CENTER; + } + else { + fmtex.dwChannelMask = SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT; + } + fmtex.SubFormat = _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + dur = (REFERENCE_TIME) + (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0))); + if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client, + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, + dur, 0, (WAVEFORMATEX*)&fmtex, 0))) + { + _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED); + goto error; + } + if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) { + _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED); + goto error; + } + if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client, + _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient), + (void**)&_saudio.backend.render_client))) + { + _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED); + goto error; + } + if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) { + _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED); + goto error; + } + _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); + _saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames; + _saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame; + + /* allocate an intermediate buffer for sample format conversion */ + _saudio.backend.thread.src_buffer = (float*) _saudio_malloc((size_t)_saudio.backend.thread.src_buffer_byte_size); + + /* create streaming thread */ + _saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0); + if (0 == _saudio.backend.thread.thread_handle) { + _SAUDIO_ERROR(WASAPI_CREATE_THREAD_FAILED); + goto error; + } + return true; +error: + _saudio_wasapi_release(); + return false; +} + +_SOKOL_PRIVATE void _saudio_wasapi_backend_shutdown(void) { + if (_saudio.backend.thread.thread_handle) { + _saudio.backend.thread.stop = true; + SetEvent(_saudio.backend.thread.buffer_end_event); + WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE); + CloseHandle(_saudio.backend.thread.thread_handle); + _saudio.backend.thread.thread_handle = 0; + } + if (_saudio.backend.audio_client) { + IAudioClient_Stop(_saudio.backend.audio_client); + } + _saudio_wasapi_release(); + CoUninitialize(); +} + +// ██ ██ ███████ ██████ █████ ██ ██ ██████ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ █████ ██████ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███ ███ ███████ ██████ ██ ██ ██████ ██████ ██ ██████ +// +// >>webaudio +#elif defined(_SAUDIO_EMSCRIPTEN) + +#ifdef __cplusplus +extern "C" { +#endif + +EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) { + SOKOL_ASSERT(_saudio.backend.buffer); + if (num_frames == _saudio.buffer_frames) { + if (_saudio_has_callback()) { + _saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels); + } + else { + const int num_bytes = num_frames * _saudio.bytes_per_frame; + if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) { + /* not enough read data available, fill the entire buffer with silence */ + _saudio_clear(_saudio.backend.buffer, (size_t)num_bytes); + } + } + int res = (int) _saudio.backend.buffer; + return res; + } + else { + return 0; + } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* setup the WebAudio context and attach a ScriptProcessorNode */ +EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), { + Module._saudio_context = null; + Module._saudio_node = null; + if (typeof AudioContext !== 'undefined') { + Module._saudio_context = new AudioContext({ + sampleRate: sample_rate, + latencyHint: 'interactive', + }); + } + else { + Module._saudio_context = null; + console.log('sokol_audio.h: no WebAudio support'); + } + if (Module._saudio_context) { + console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate); + Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels); + Module._saudio_node.onaudioprocess = (event) => { + const num_frames = event.outputBuffer.length; + const ptr = __saudio_emsc_pull(num_frames); + if (ptr) { + const num_channels = event.outputBuffer.numberOfChannels; + for (let chn = 0; chn < num_channels; chn++) { + const chan = event.outputBuffer.getChannelData(chn); + for (let i = 0; i < num_frames; i++) { + chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)] + } + } + } + }; + Module._saudio_node.connect(Module._saudio_context.destination); + + // in some browsers, WebAudio needs to be activated on a user action + const resume_webaudio = () => { + if (Module._saudio_context) { + if (Module._saudio_context.state === 'suspended') { + Module._saudio_context.resume(); + } + } + }; + document.addEventListener('click', resume_webaudio, {once:true}); + document.addEventListener('touchend', resume_webaudio, {once:true}); + document.addEventListener('keydown', resume_webaudio, {once:true}); + return 1; + } + else { + return 0; + } +}) + +/* shutdown the WebAudioContext and ScriptProcessorNode */ +EM_JS(void, saudio_js_shutdown, (void), { + \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F + const ctx = Module._saudio_context; + if (ctx !== null) { + if (Module._saudio_node) { + Module._saudio_node.disconnect(); + } + ctx.close(); + Module._saudio_context = null; + Module._saudio_node = null; + } +}) + +/* get the actual sample rate back from the WebAudio context */ +EM_JS(int, saudio_js_sample_rate, (void), { + if (Module._saudio_context) { + return Module._saudio_context.sampleRate; + } + else { + return 0; + } +}) + +/* get the actual buffer size in number of frames */ +EM_JS(int, saudio_js_buffer_frames, (void), { + if (Module._saudio_node) { + return Module._saudio_node.bufferSize; + } + else { + return 0; + } +}) + +/* return 1 if the WebAudio context is currently suspended, else 0 */ +EM_JS(int, saudio_js_suspended, (void), { + if (Module._saudio_context) { + if (Module._saudio_context.state === 'suspended') { + return 1; + } + else { + return 0; + } + } +}) + +_SOKOL_PRIVATE bool _saudio_webaudio_backend_init(void) { + if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) { + _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels; + _saudio.sample_rate = saudio_js_sample_rate(); + _saudio.buffer_frames = saudio_js_buffer_frames(); + const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame); + _saudio.backend.buffer = (uint8_t*) _saudio_malloc(buf_size); + return true; + } + else { + return false; + } +} + +_SOKOL_PRIVATE void _saudio_webaudio_backend_shutdown(void) { + saudio_js_shutdown(); + if (_saudio.backend.buffer) { + _saudio_free(_saudio.backend.buffer); + _saudio.backend.buffer = 0; + } +} + +// █████ █████ ██ ██ ██████ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██████ ██████ ██ ██████ +// +// >>aaudio +#elif defined(_SAUDIO_ANDROID) + +_SOKOL_PRIVATE aaudio_data_callback_result_t _saudio_aaudio_data_callback(AAudioStream* stream, void* user_data, void* audio_data, int32_t num_frames) { + _SOKOL_UNUSED(user_data); + _SOKOL_UNUSED(stream); + if (_saudio_has_callback()) { + _saudio_stream_callback((float*)audio_data, (int)num_frames, _saudio.num_channels); + } + else { + uint8_t* ptr = (uint8_t*)audio_data; + int num_bytes = _saudio.bytes_per_frame * num_frames; + if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) { + // not enough read data available, fill the entire buffer with silence + memset(ptr, 0, (size_t)num_bytes); + } + } + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +_SOKOL_PRIVATE bool _saudio_aaudio_start_stream(void) { + if (AAudioStreamBuilder_openStream(_saudio.backend.builder, &_saudio.backend.stream) != AAUDIO_OK) { + _SAUDIO_ERROR(AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED); + return false; + } + AAudioStream_requestStart(_saudio.backend.stream); + return true; +} + +_SOKOL_PRIVATE void _saudio_aaudio_stop_stream(void) { + if (_saudio.backend.stream) { + AAudioStream_requestStop(_saudio.backend.stream); + AAudioStream_close(_saudio.backend.stream); + _saudio.backend.stream = 0; + } +} + +_SOKOL_PRIVATE void* _saudio_aaudio_restart_stream_thread_fn(void* param) { + _SOKOL_UNUSED(param); + _SAUDIO_WARN(AAUDIO_RESTARTING_STREAM_AFTER_ERROR); + pthread_mutex_lock(&_saudio.backend.mutex); + _saudio_aaudio_stop_stream(); + _saudio_aaudio_start_stream(); + pthread_mutex_unlock(&_saudio.backend.mutex); + return 0; +} + +_SOKOL_PRIVATE void _saudio_aaudio_error_callback(AAudioStream* stream, void* user_data, aaudio_result_t error) { + _SOKOL_UNUSED(stream); + _SOKOL_UNUSED(user_data); + if (error == AAUDIO_ERROR_DISCONNECTED) { + if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_aaudio_restart_stream_thread_fn, 0)) { + _SAUDIO_ERROR(AAUDIO_PTHREAD_CREATE_FAILED); + } + } +} + +_SOKOL_PRIVATE void _saudio_aaudio_backend_shutdown(void) { + pthread_mutex_lock(&_saudio.backend.mutex); + _saudio_aaudio_stop_stream(); + pthread_mutex_unlock(&_saudio.backend.mutex); + if (_saudio.backend.builder) { + AAudioStreamBuilder_delete(_saudio.backend.builder); + _saudio.backend.builder = 0; + } + pthread_mutex_destroy(&_saudio.backend.mutex); +} + +_SOKOL_PRIVATE bool _saudio_aaudio_backend_init(void) { + _SAUDIO_INFO(USING_AAUDIO_BACKEND); + + _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float); + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init(&_saudio.backend.mutex, &attr); + + if (AAudio_createStreamBuilder(&_saudio.backend.builder) != AAUDIO_OK) { + _SAUDIO_ERROR(AAUDIO_CREATE_STREAMBUILDER_FAILED); + _saudio_aaudio_backend_shutdown(); + return false; + } + + AAudioStreamBuilder_setFormat(_saudio.backend.builder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setSampleRate(_saudio.backend.builder, _saudio.sample_rate); + AAudioStreamBuilder_setChannelCount(_saudio.backend.builder, _saudio.num_channels); + AAudioStreamBuilder_setBufferCapacityInFrames(_saudio.backend.builder, _saudio.buffer_frames * 2); + AAudioStreamBuilder_setFramesPerDataCallback(_saudio.backend.builder, _saudio.buffer_frames); + AAudioStreamBuilder_setDataCallback(_saudio.backend.builder, _saudio_aaudio_data_callback, 0); + AAudioStreamBuilder_setErrorCallback(_saudio.backend.builder, _saudio_aaudio_error_callback, 0); + + if (!_saudio_aaudio_start_stream()) { + _saudio_aaudio_backend_shutdown(); + return false; + } + + return true; +} + +// ██████ ██████ ██████ ███████ █████ ██ ██ ██████ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██████ █████ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ██ ███████ ██ ██ ██████ ██████ ██ ██████ +// +// >>coreaudio +#elif defined(_SAUDIO_APPLE) + +#if defined(_SAUDIO_IOS) +#if __has_feature(objc_arc) +#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +@interface _saudio_interruption_handler : NSObject { } +@end + +@implementation _saudio_interruption_handler +-(id)init { + self = [super init]; + AVAudioSession* session = [AVAudioSession sharedInstance]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session]; + return self; +} + +-(void)dealloc { + [self remove_handler]; + #if !__has_feature(objc_arc) + [super dealloc]; + #endif +} + +-(void)remove_handler { + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil]; +} + +-(void)handle_interruption:(NSNotification*)notification { + AVAudioSession* session = [AVAudioSession sharedInstance]; + SOKOL_ASSERT(session); + NSDictionary* dict = notification.userInfo; + SOKOL_ASSERT(dict); + NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue]; + switch (type) { + case AVAudioSessionInterruptionTypeBegan: + if (_saudio.backend.ca_audio_queue) { + AudioQueuePause(_saudio.backend.ca_audio_queue); + } + [session setActive:false error:nil]; + break; + case AVAudioSessionInterruptionTypeEnded: + [session setActive:true error:nil]; + if (_saudio.backend.ca_audio_queue) { + AudioQueueStart(_saudio.backend.ca_audio_queue, NULL); + } + break; + default: + break; + } +} +@end +#endif // _SAUDIO_IOS + +/* NOTE: the buffer data callback is called on a separate thread! */ +_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, _saudio_AudioQueueRef queue, _saudio_AudioQueueBufferRef buffer) { + _SOKOL_UNUSED(user_data); + if (_saudio_has_callback()) { + const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame; + const int num_channels = _saudio.num_channels; + _saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels); + } + else { + uint8_t* ptr = (uint8_t*)buffer->mAudioData; + int num_bytes = (int) buffer->mAudioDataByteSize; + if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) { + /* not enough read data available, fill the entire buffer with silence */ + _saudio_clear(ptr, (size_t)num_bytes); + } + } + AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); +} + +_SOKOL_PRIVATE void _saudio_coreaudio_backend_shutdown(void) { + if (_saudio.backend.ca_audio_queue) { + AudioQueueStop(_saudio.backend.ca_audio_queue, true); + AudioQueueDispose(_saudio.backend.ca_audio_queue, false); + _saudio.backend.ca_audio_queue = 0; + } + #if defined(_SAUDIO_IOS) + /* remove interruption handler */ + if (_saudio.backend.ca_interruption_handler != nil) { + [_saudio.backend.ca_interruption_handler remove_handler]; + _SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler); + } + /* deactivate audio session */ + AVAudioSession* session = [AVAudioSession sharedInstance]; + SOKOL_ASSERT(session); + [session setActive:false error:nil];; + #endif // _SAUDIO_IOS +} + +_SOKOL_PRIVATE bool _saudio_coreaudio_backend_init(void) { + SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue); + + #if defined(_SAUDIO_IOS) + /* activate audio session */ + AVAudioSession* session = [AVAudioSession sharedInstance]; + SOKOL_ASSERT(session != nil); + [session setCategory: AVAudioSessionCategoryPlayback error:nil]; + [session setActive:true error:nil]; + + /* create interruption handler */ + _saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init]; + #endif + + /* create an audio queue with fp32 samples */ + _saudio_AudioStreamBasicDescription fmt; + _saudio_clear(&fmt, sizeof(fmt)); + fmt.mSampleRate = (double) _saudio.sample_rate; + fmt.mFormatID = _saudio_kAudioFormatLinearPCM; + fmt.mFormatFlags = _saudio_kLinearPCMFormatFlagIsFloat | _saudio_kAudioFormatFlagIsPacked; + fmt.mFramesPerPacket = 1; + fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels; + fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels; + fmt.mBytesPerPacket = fmt.mBytesPerFrame; + fmt.mBitsPerChannel = 32; + _saudio_OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue); + if (0 != res) { + _SAUDIO_ERROR(COREAUDIO_NEW_OUTPUT_FAILED); + return false; + } + SOKOL_ASSERT(_saudio.backend.ca_audio_queue); + + /* create 2 audio buffers */ + for (int i = 0; i < 2; i++) { + _saudio_AudioQueueBufferRef buf = NULL; + const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame; + res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf); + if (0 != res) { + _SAUDIO_ERROR(COREAUDIO_ALLOCATE_BUFFER_FAILED); + _saudio_coreaudio_backend_shutdown(); + return false; + } + buf->mAudioDataByteSize = buf_byte_size; + _saudio_clear(buf->mAudioData, buf->mAudioDataByteSize); + AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL); + } + + /* init or modify actual playback parameters */ + _saudio.bytes_per_frame = (int)fmt.mBytesPerFrame; + + /* ...and start playback */ + res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL); + if (0 != res) { + _SAUDIO_ERROR(COREAUDIO_START_FAILED); + _saudio_coreaudio_backend_shutdown(); + return false; + } + return true; +} + +#else +#error "unsupported platform" +#endif + +bool _saudio_backend_init(void) { + #if defined(SOKOL_DUMMY_BACKEND) + return _saudio_dummy_backend_init(); + #elif defined(_SAUDIO_LINUX) + return _saudio_alsa_backend_init(); + #elif defined(_SAUDIO_WINDOWS) + return _saudio_wasapi_backend_init(); + #elif defined(_SAUDIO_EMSCRIPTEN) + return _saudio_webaudio_backend_init(); + #elif defined(_SAUDIO_ANDROID) + return _saudio_aaudio_backend_init(); + #elif defined(_SAUDIO_APPLE) + return _saudio_coreaudio_backend_init(); + #else + #error "unknown platform" + #endif +} + +void _saudio_backend_shutdown(void) { + #if defined(SOKOL_DUMMY_BACKEND) + _saudio_dummy_backend_shutdown(); + #elif defined(_SAUDIO_LINUX) + _saudio_alsa_backend_shutdown(); + #elif defined(_SAUDIO_WINDOWS) + _saudio_wasapi_backend_shutdown(); + #elif defined(_SAUDIO_EMSCRIPTEN) + _saudio_webaudio_backend_shutdown(); + #elif defined(_SAUDIO_ANDROID) + _saudio_aaudio_backend_shutdown(); + #elif defined(_SAUDIO_APPLE) + _saudio_coreaudio_backend_shutdown(); + #else + #error "unknown platform" + #endif +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) { + SOKOL_ASSERT(!_saudio.valid); + SOKOL_ASSERT(!_saudio.setup_called); + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + _saudio_clear(&_saudio, sizeof(_saudio)); + _saudio.setup_called = true; + _saudio.desc = *desc; + _saudio.stream_cb = desc->stream_cb; + _saudio.stream_userdata_cb = desc->stream_userdata_cb; + _saudio.user_data = desc->user_data; + _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE); + _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES); + _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES); + _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS); + _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1); + _saudio_fifo_init_mutex(&_saudio.fifo); + if (_saudio_backend_init()) { + /* the backend might not support the requested exact buffer size, + make sure the actual buffer size is still a multiple of + the requested packet size + */ + if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) { + _SAUDIO_ERROR(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE); + _saudio_backend_shutdown(); + return; + } + SOKOL_ASSERT(_saudio.bytes_per_frame > 0); + _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets); + _saudio.valid = true; + } + else { + _saudio_fifo_destroy_mutex(&_saudio.fifo); + } +} + +SOKOL_API_IMPL void saudio_shutdown(void) { + SOKOL_ASSERT(_saudio.setup_called); + _saudio.setup_called = false; + if (_saudio.valid) { + _saudio_backend_shutdown(); + _saudio_fifo_shutdown(&_saudio.fifo); + _saudio_fifo_destroy_mutex(&_saudio.fifo); + _saudio.valid = false; + } +} + +SOKOL_API_IMPL bool saudio_isvalid(void) { + return _saudio.valid; +} + +SOKOL_API_IMPL void* saudio_userdata(void) { + SOKOL_ASSERT(_saudio.setup_called); + return _saudio.desc.user_data; +} + +SOKOL_API_IMPL saudio_desc saudio_query_desc(void) { + SOKOL_ASSERT(_saudio.setup_called); + return _saudio.desc; +} + +SOKOL_API_IMPL int saudio_sample_rate(void) { + SOKOL_ASSERT(_saudio.setup_called); + return _saudio.sample_rate; +} + +SOKOL_API_IMPL int saudio_buffer_frames(void) { + SOKOL_ASSERT(_saudio.setup_called); + return _saudio.buffer_frames; +} + +SOKOL_API_IMPL int saudio_channels(void) { + SOKOL_ASSERT(_saudio.setup_called); + return _saudio.num_channels; +} + +SOKOL_API_IMPL bool saudio_suspended(void) { + SOKOL_ASSERT(_saudio.setup_called); + #if defined(_SAUDIO_EMSCRIPTEN) + if (_saudio.valid) { + return 1 == saudio_js_suspended(); + } + else { + return false; + } + #else + return false; + #endif +} + +SOKOL_API_IMPL int saudio_expect(void) { + SOKOL_ASSERT(_saudio.setup_called); + if (_saudio.valid) { + const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame; + return num_frames; + } + else { + return 0; + } +} + +SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) { + SOKOL_ASSERT(_saudio.setup_called); + SOKOL_ASSERT(frames && (num_frames > 0)); + if (_saudio.valid) { + const int num_bytes = num_frames * _saudio.bytes_per_frame; + const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes); + return num_written / _saudio.bytes_per_frame; + } + else { + return 0; + } +} + +#undef _saudio_def +#undef _saudio_def_flt + +#if defined(_SAUDIO_WINDOWS) +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif + +#endif /* SOKOL_AUDIO_IMPL */ diff --git a/modules/sokol-jai/sokol/c/sokol_debugtext.c b/modules/sokol-jai/sokol/c/sokol_debugtext.c new file mode 100644 index 0000000..42ad569 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_debugtext.c @@ -0,0 +1,7 @@ +#if defined(IMPL) +#define SOKOL_DEBUGTEXT_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_gfx.h" +#include "sokol_debugtext.h" + diff --git a/modules/sokol-jai/sokol/c/sokol_debugtext.h b/modules/sokol-jai/sokol/c/sokol_debugtext.h new file mode 100644 index 0000000..904b778 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_debugtext.h @@ -0,0 +1,4989 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_DEBUGTEXT_IMPL) +#define SOKOL_DEBUGTEXT_IMPL +#endif +#ifndef SOKOL_DEBUGTEXT_INCLUDED +/* + sokol_debugtext.h - simple ASCII debug text rendering on top of sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_DEBUGTEXT_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + ...optionally provide the following macros to override defaults: + + SOKOL_VSNPRINTF - the function name of an alternative vsnprintf() function (default: vsnprintf) + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_DEBUGTEXT_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_DEBUGTEXT_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + If sokol_debugtext.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_DEBUGTEXT_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before including sokol_debugtext.h: + + sokol_gfx.h + + FEATURES AND CONCEPTS + ===================== + - renders 8-bit ASCII text as fixed-size 8x8 pixel characters + - comes with 6 embedded 8-bit home computer fonts (each taking up 2 KBytes) + - easily plug in your own fonts + - create multiple contexts for rendering text in different layers or render passes + + STEP BY STEP + ============ + + --- to initialize sokol-debugtext, call sdtx_setup() *after* initializing + sokol-gfx: + + sdtx_setup(&(sdtx_desc_t){ ... }); + + To see any warnings and errors, you should always install a logging callback. + The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sdtx_setup(&(sdtx_desc_t){ + .logger.func = slog_func, + }); + + --- configure sokol-debugtext by populating the sdtx_desc_t struct: + + .context_pool_size (default: 8) + The max number of text contexts that can be created. + + .printf_buf_size (default: 4096) + The size of the internal text formatting buffer used by + sdtx_printf() and sdtx_vprintf(). + + .fonts (default: none) + An array of sdtx_font_desc_t structs used to configure the + fonts that can be used for rendering. To use all builtin + fonts call sdtx_setup() like this (in C99): + + sdtx_setup(&(sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = sdtx_font_kc854(), + [2] = sdtx_font_z1013(), + [3] = sdtx_font_cpc(), + [4] = sdtx_font_c64(), + [5] = sdtx_font_oric() + } + }); + + For documentation on how to use you own font data, search + below for "USING YOUR OWN FONT DATA". + + .context + The setup parameters for the default text context. This will + be active right after sdtx_setup(), or when calling + sdtx_set_context(SDTX_DEFAULT_CONTEXT): + + .max_commands (default: 4096) + The max number of render commands that can be recorded + into the internal command buffer. This directly translates + to the number of render layer changes in a single frame. + + .char_buf_size (default: 4096) + The number of characters that can be rendered per frame in this + context, defines the size of an internal fixed-size vertex + buffer. Any additional characters will be silently ignored. + + .canvas_width (default: 640) + .canvas_height (default: 480) + The 'virtual canvas size' in pixels. This defines how big + characters will be rendered relative to the default framebuffer + dimensions. Each character occupies a grid of 8x8 'virtual canvas + pixels' (so a virtual canvas size of 640x480 means that 80x60 characters + fit on the screen). For rendering in a resizeable window, you + should dynamically update the canvas size in each frame by + calling sdtx_canvas(w, h). + + .tab_width (default: 4) + The width of a tab character in number of character cells. + + .color_format (default: 0) + .depth_format (default: 0) + .sample_count (default: 0) + The pixel format description for the default context needed + for creating the context's sg_pipeline object. When + rendering to the default framebuffer you can leave those + zero-initialized, in this case the proper values will be + filled in by sokol-gfx. You only need to provide non-default + values here when rendering to render targets with different + pixel format attributes than the default framebuffer. + + --- Before starting to render text, optionally call sdtx_canvas() to + dynamically resize the virtual canvas. This is recommended when + rendering to a resizeable window. The virtual canvas size can + also be used to scale text in relation to the display resolution. + + Examples when using sokol-app: + + - to render characters at 8x8 'physical pixels': + + sdtx_canvas(sapp_width(), sapp_height()); + + - to render characters at 16x16 physical pixels: + + sdtx_canvas(sapp_width()/2.0f, sapp_height()/2.0f); + + Do *not* use integer math here, since this will not look nice + when the render target size isn't divisible by 2. + + --- Optionally define the origin for the character grid with: + + sdtx_origin(x, y); + + The provided coordinates are in character grid cells, not in + virtual canvas pixels. E.g. to set the origin to 2 character tiles + from the left and top border: + + sdtx_origin(2, 2); + + You can define fractions, e.g. to start rendering half + a character tile from the top-left corner: + + sdtx_origin(0.5f, 0.5f); + + --- Optionally set a different font by calling: + + sdtx_font(font_index) + + sokol-debugtext provides 8 font slots which can be populated + with the builtin fonts or with user-provided font data, so + 'font_index' must be a number from 0 to 7. + + --- Position the text cursor with one of the following calls. All arguments + are in character grid cells as floats and relative to the + origin defined with sdtx_origin(): + + sdtx_pos(x, y) - sets absolute cursor position + sdtx_pos_x(x) - only set absolute x cursor position + sdtx_pos_y(y) - only set absolute y cursor position + + sdtx_move(x, y) - move cursor relative in x and y direction + sdtx_move_x(x) - move cursor relative only in x direction + sdtx_move_y(y) - move cursor relative only in y direction + + sdtx_crlf() - set cursor to beginning of next line + (same as sdtx_pos_x(0) + sdtx_move_y(1)) + sdtx_home() - resets the cursor to the origin + (same as sdtx_pos(0, 0)) + + --- Set a new text color with any of the following functions: + + sdtx_color3b(r, g, b) - RGB 0..255, A=255 + sdtx_color3f(r, g, b) - RGB 0.0f..1.0f, A=1.0f + sdtx_color4b(r, g, b, a) - RGBA 0..255 + sdtx_color4f(r, g, b, a) - RGBA 0.0f..1.0f + sdtx_color1i(uint32_t rgba) - ABGR (0xAABBGGRR) + + --- Output 8-bit ASCII text with the following functions: + + sdtx_putc(c) - output a single character + + sdtx_puts(str) - output a null-terminated C string, note that + this will *not* append a newline (so it behaves + differently than the CRT's puts() function) + + sdtx_putr(str, len) - 'put range' output the first 'len' characters of + a C string or until the zero character is encountered + + sdtx_printf(fmt, ...) - output with printf-formatting, note that you + can inject your own printf-compatible function + by overriding the SOKOL_VSNPRINTF define before + including the implementation + + sdtx_vprintf(fmt, args) - same as sdtx_printf() but with the arguments + provided in a va_list + + - Note that the text will not yet be rendered, only recorded for rendering + at a later time, the actual rendering happens when sdtx_draw() is called + inside a sokol-gfx render pass. + - This means also you can output text anywhere in the frame, it doesn't + have to be inside a render pass. + - Note that character codes <32 are reserved as control characters + and won't render anything. Currently only the following control + characters are implemented: + + \r - carriage return (same as sdtx_pos_x(0)) + \n - carriage return + line feed (same as stdx_crlf()) + \t - a tab character + + --- You can 'record' text into render layers, this allows to mix/interleave + sokol-debugtext rendering with other rendering operations inside + sokol-gfx render passes. To start recording text into a different render + layer, call: + + sdtx_layer(int layer_id) + + ...outside a sokol-gfx render pass. + + --- finally, from within a sokol-gfx render pass, call: + + sdtx_draw() + + ...for non-layered rendering, or to draw a specific layer: + + sdtx_draw_layer(int layer_id) + + NOTE that sdtx_draw() is equivalent to: + + sdtx_draw_layer(0) + + ...so sdtx_draw() will *NOT* render all text layers, instead it will + only render the 'default layer' 0. + + --- at the end of a frame (defined by the call to sg_commit()), sokol-debugtext + will rewind all contexts: + + - the internal vertex index is set to 0 + - the internal command index is set to 0 + - the current layer id is set to 0 + - the current font is set to 0 + - the cursor position is reset + + + RENDERING WITH MULTIPLE CONTEXTS + ================================ + Use multiple text contexts if you need to render debug text in different + sokol-gfx render passes, or want to render text to different layers + in the same render pass, each with its own set of parameters. + + To create a new text context call: + + sdtx_context ctx = sdtx_make_context(&(sdtx_context_desc_t){ ... }); + + The creation parameters in the sdtx_context_desc_t struct are the same + as already described above in the sdtx_setup() function: + + .char_buf_size -- max number of characters rendered in one frame, default: 4096 + .canvas_width -- the initial virtual canvas width, default: 640 + .canvas_height -- the initial virtual canvas height, default: 400 + .tab_width -- tab width in number of characters, default: 4 + .color_format -- color pixel format of target render pass + .depth_format -- depth pixel format of target render pass + .sample_count -- MSAA sample count of target render pass + + To make a new context the active context, call: + + sdtx_set_context(ctx) + + ...and after that call the text output functions as described above, and + finally, inside a sokol-gfx render pass, call sdtx_draw() to actually + render the text for this context. + + A context keeps track of the following parameters: + + - the active font + - the virtual canvas size + - the origin position + - the current cursor position + - the current tab width + - the current color + - and the current layer-id + + You can get the currently active context with: + + sdtx_get_context() + + To make the default context current, call sdtx_set_context() with the + special SDTX_DEFAULT_CONTEXT handle: + + sdtx_set_context(SDTX_DEFAULT_CONTEXT) + + Alternatively, use the function sdtx_default_context() to get the default + context handle: + + sdtx_set_context(sdtx_default_context()); + + To destroy a context, call: + + sdtx_destroy_context(ctx) + + If a context is set as active that no longer exists, all sokol-debugtext + functions that require an active context will silently fail. + + You can directly draw the recorded text in a specific context without + setting the active context: + + sdtx_context_draw(ctx) + sdtx_context_draw_layer(ctx, layer_id) + + USING YOUR OWN FONT DATA + ======================== + + Instead of the built-in fonts you can also plug your own font data + into sokol-debugtext by providing one or several sdtx_font_desc_t + structures in the sdtx_setup call. + + For instance to use a built-in font at slot 0, and a user-font at + font slot 1, the sdtx_setup() call might look like this: + + sdtx_setup(&sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = { + .data = { + .ptr = my_font_data, + .size = sizeof(my_font_data) + }, + .first_char = ..., + .last_char = ... + } + } + }); + + Where 'my_font_data' is a byte array where every character is described + by 8 bytes arranged like this: + + bits + 7 6 5 4 3 2 1 0 + . . . X X . . . byte 0: 0x18 + . . X X X X . . byte 1: 0x3C + . X X . . X X . byte 2: 0x66 + . X X . . X X . byte 3: 0x66 + . X X X X X X . byte 4: 0x7E + . X X . . X X . byte 5: 0x66 + . X X . . X X . byte 6: 0x66 + . . . . . . . . byte 7: 0x00 + + A complete font consists of 256 characters, resulting in 2048 bytes for + the font data array (but note that the character codes 0..31 will never + be rendered). + + If you provide such a complete font data array, you can drop the .first_char + and .last_char initialization parameters since those default to 0 and 255, + note that you can also use the SDTX_RANGE() helper macro to build the + .data item: + + sdtx_setup(&sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = { + .data = SDTX_RANGE(my_font_data) + } + } + }); + + If the font doesn't define all 256 character tiles, or you don't need an + entire 256-character font and want to save a couple of bytes, use the + .first_char and .last_char initialization parameters to define a sub-range. + For instance if the font only contains the characters between the Space + (ASCII code 32) and uppercase character 'Z' (ASCII code 90): + + sdtx_setup(&sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = { + .data = SDTX_RANGE(my_font_data), + .first_char = 32, // could also write ' ' + .last_char = 90 // could also write 'Z' + } + } + }); + + Character tiles that haven't been defined in the font will be rendered + as a solid 8x8 quad. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sdtx_setup(&(sdtx_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sdtx_setup(&(sdtx_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sdtx' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SDTX_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_debugtext.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-debugtext like this: + + sdtx_setup(&(sdtx_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2020 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_DEBUGTEXT_INCLUDED (1) +#include +#include +#include // size_t +#include // va_list + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_debugtext.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_DEBUGTEXT_API_DECL) +#define SOKOL_DEBUGTEXT_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_DEBUGTEXT_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_DEBUGTEXT_IMPL) +#define SOKOL_DEBUGTEXT_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_DEBUGTEXT_API_DECL __declspec(dllimport) +#else +#define SOKOL_DEBUGTEXT_API_DECL extern +#endif +#endif + +#if defined(__GNUC__) +#define SOKOL_DEBUGTEXT_PRINTF_ATTR __attribute__((format(printf, 1, 2))) +#else +#define SOKOL_DEBUGTEXT_PRINTF_ATTR +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sdtx_log_item_t + + Log items are defined via X-Macros, and expanded to an + enum 'sdtx_log_item' - and in debug mode only - corresponding strings. + + Used as parameter in the logging callback. +*/ +#define _SDTX_LOG_ITEMS \ + _SDTX_LOGITEM_XMACRO(OK, "Ok") \ + _SDTX_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SDTX_LOGITEM_XMACRO(ADD_COMMIT_LISTENER_FAILED, "sg_add_commit_listener() failed") \ + _SDTX_LOGITEM_XMACRO(COMMAND_BUFFER_FULL, "command buffer full (adjust via sdtx_context_desc_t.max_commands)") \ + _SDTX_LOGITEM_XMACRO(CONTEXT_POOL_EXHAUSTED, "context pool exhausted (adjust via sdtx_desc_t.context_pool_size)") \ + _SDTX_LOGITEM_XMACRO(CANNOT_DESTROY_DEFAULT_CONTEXT, "cannot destroy default context") \ + +#define _SDTX_LOGITEM_XMACRO(item,msg) SDTX_LOGITEM_##item, +typedef enum sdtx_log_item_t { + _SDTX_LOG_ITEMS +} sdtx_log_item_t; +#undef _SDTX_LOGITEM_XMACRO + +/* + sdtx_logger_t + + Used in sdtx_desc_t to provide a custom logging and error reporting + callback to sokol-debugtext. +*/ +typedef struct sdtx_logger_t { + void (*func)( + const char* tag, // always "sdtx" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SDTX_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_debugtext.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sdtx_logger_t; + +/* a rendering context handle */ +typedef struct sdtx_context { uint32_t id; } sdtx_context; + +/* the default context handle */ +static const sdtx_context SDTX_DEFAULT_CONTEXT = { 0x00010001 }; + +/* + sdtx_range is a pointer-size-pair struct used to pass memory + blobs into sokol-debugtext. When initialized from a value type + (array or struct), use the SDTX_RANGE() macro to build + an sdtx_range struct. +*/ +typedef struct sdtx_range { + const void* ptr; + size_t size; +} sdtx_range; + +// disabling this for every includer isn't great, but the warning is also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ +#pragma warning(disable:4204) /* VS2015: nonstandard extension used: non-constant aggregate initializer */ +#endif +#if defined(__cplusplus) +#define SDTX_RANGE(x) sdtx_range{ &x, sizeof(x) } +#else +#define SDTX_RANGE(x) (sdtx_range){ &x, sizeof(x) } +#endif + +/* + sdtx_font_desc_t + + Describes the pixel data of a font. A font consists of up to + 256 8x8 character tiles, where each character tile is described + by 8 consecutive bytes, each byte describing 8 pixels. + + For instance the character 'A' could look like this (this is also + how most home computers used to describe their fonts in ROM): + + bits + 7 6 5 4 3 2 1 0 + . . . X X . . . byte 0: 0x18 + . . X X X X . . byte 1: 0x3C + . X X . . X X . byte 2: 0x66 + . X X . . X X . byte 3: 0x66 + . X X X X X X . byte 4: 0x7E + . X X . . X X . byte 5: 0x66 + . X X . . X X . byte 6: 0x66 + . . . . . . . . byte 7: 0x00 + */ +#define SDTX_MAX_FONTS (8) + +typedef struct sdtx_font_desc_t { + sdtx_range data; // pointer to and size of font pixel data + uint8_t first_char; // first character index in font pixel data + uint8_t last_char; // last character index in font pixel data, inclusive (default: 255) +} sdtx_font_desc_t; + +/* + sdtx_context_desc_t + + Describes the initialization parameters of a rendering context. Creating + additional rendering contexts is useful if you want to render in + different sokol-gfx rendering passes, or when rendering several layers + of text. +*/ +typedef struct sdtx_context_desc_t { + int max_commands; // max number of draw commands, each layer transition counts as a command, default: 4096 + int char_buf_size; // max number of characters rendered in one frame, default: 4096 + float canvas_width; // the initial virtual canvas width, default: 640 + float canvas_height; // the initial virtual canvas height, default: 400 + int tab_width; // tab width in number of characters, default: 4 + sg_pixel_format color_format; // color pixel format of target render pass + sg_pixel_format depth_format; // depth pixel format of target render pass + int sample_count; // MSAA sample count of target render pass +} sdtx_context_desc_t; + +/* + sdtx_allocator_t + + Used in sdtx_desc_t to provide custom memory-alloc and -free functions + to sokol_debugtext.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sdtx_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sdtx_allocator_t; + +/* + sdtx_desc_t + + Describes the sokol-debugtext API initialization parameters. Passed + to the sdtx_setup() function. + + NOTE: to populate the fonts item array with builtin fonts, use any + of the following functions: + + sdtx_font_kc853() + sdtx_font_kc854() + sdtx_font_z1013() + sdtx_font_cpc() + sdtx_font_c64() + sdtx_font_oric() +*/ +typedef struct sdtx_desc_t { + int context_pool_size; // max number of rendering contexts that can be created, default: 8 + int printf_buf_size; // size of internal buffer for snprintf(), default: 4096 + sdtx_font_desc_t fonts[SDTX_MAX_FONTS]; // up to 8 fonts descriptions + sdtx_context_desc_t context; // the default context creation parameters + sdtx_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + sdtx_logger_t logger; // optional log override function (default: NO LOGGING) +} sdtx_desc_t; + +/* initialization/shutdown */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_setup(const sdtx_desc_t* desc); +SOKOL_DEBUGTEXT_API_DECL void sdtx_shutdown(void); + +/* builtin font data (use to populate sdtx_desc.font[]) */ +SOKOL_DEBUGTEXT_API_DECL sdtx_font_desc_t sdtx_font_kc853(void); +SOKOL_DEBUGTEXT_API_DECL sdtx_font_desc_t sdtx_font_kc854(void); +SOKOL_DEBUGTEXT_API_DECL sdtx_font_desc_t sdtx_font_z1013(void); +SOKOL_DEBUGTEXT_API_DECL sdtx_font_desc_t sdtx_font_cpc(void); +SOKOL_DEBUGTEXT_API_DECL sdtx_font_desc_t sdtx_font_c64(void); +SOKOL_DEBUGTEXT_API_DECL sdtx_font_desc_t sdtx_font_oric(void); + +/* context functions */ +SOKOL_DEBUGTEXT_API_DECL sdtx_context sdtx_make_context(const sdtx_context_desc_t* desc); +SOKOL_DEBUGTEXT_API_DECL void sdtx_destroy_context(sdtx_context ctx); +SOKOL_DEBUGTEXT_API_DECL void sdtx_set_context(sdtx_context ctx); +SOKOL_DEBUGTEXT_API_DECL sdtx_context sdtx_get_context(void); +SOKOL_DEBUGTEXT_API_DECL sdtx_context sdtx_default_context(void); + +/* drawing functions (call inside sokol-gfx render pass) */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_draw(void); +SOKOL_DEBUGTEXT_API_DECL void sdtx_context_draw(sdtx_context ctx); +SOKOL_DEBUGTEXT_API_DECL void sdtx_draw_layer(int layer_id); +SOKOL_DEBUGTEXT_API_DECL void sdtx_context_draw_layer(sdtx_context ctx, int layer_id); + +/* switch render layer */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_layer(int layer_id); + +/* switch to a different font */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_font(int font_index); + +/* set a new virtual canvas size in screen pixels */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_canvas(float w, float h); + +/* set a new origin in character grid coordinates */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_origin(float x, float y); + +/* cursor movement functions (relative to origin in character grid coordinates) */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_home(void); +SOKOL_DEBUGTEXT_API_DECL void sdtx_pos(float x, float y); +SOKOL_DEBUGTEXT_API_DECL void sdtx_pos_x(float x); +SOKOL_DEBUGTEXT_API_DECL void sdtx_pos_y(float y); +SOKOL_DEBUGTEXT_API_DECL void sdtx_move(float dx, float dy); +SOKOL_DEBUGTEXT_API_DECL void sdtx_move_x(float dx); +SOKOL_DEBUGTEXT_API_DECL void sdtx_move_y(float dy); +SOKOL_DEBUGTEXT_API_DECL void sdtx_crlf(void); + +/* set the current text color */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_color3b(uint8_t r, uint8_t g, uint8_t b); // RGB 0..255, A=255 +SOKOL_DEBUGTEXT_API_DECL void sdtx_color3f(float r, float g, float b); // RGB 0.0f..1.0f, A=1.0f +SOKOL_DEBUGTEXT_API_DECL void sdtx_color4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); // RGBA 0..255 +SOKOL_DEBUGTEXT_API_DECL void sdtx_color4f(float r, float g, float b, float a); // RGBA 0.0f..1.0f +SOKOL_DEBUGTEXT_API_DECL void sdtx_color1i(uint32_t rgba); // ABGR 0xAABBGGRR + +/* text rendering */ +SOKOL_DEBUGTEXT_API_DECL void sdtx_putc(char c); +SOKOL_DEBUGTEXT_API_DECL void sdtx_puts(const char* str); // does NOT append newline! +SOKOL_DEBUGTEXT_API_DECL void sdtx_putr(const char* str, int len); // 'put range', also stops at zero-char +SOKOL_DEBUGTEXT_API_DECL int sdtx_printf(const char* fmt, ...) SOKOL_DEBUGTEXT_PRINTF_ATTR; +SOKOL_DEBUGTEXT_API_DECL int sdtx_vprintf(const char* fmt, va_list args); + +#ifdef __cplusplus +} /* extern "C" */ +/* C++ const-ref wrappers */ +inline void sdtx_setup(const sdtx_desc_t& desc) { return sdtx_setup(&desc); } +inline sdtx_context sdtx_make_context(const sdtx_context_desc_t& desc) { return sdtx_make_context(&desc); } +#endif +#endif /* SOKOL_DEBUGTEXT_INCLUDED */ + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_DEBUGTEXT_IMPL +#define SOKOL_DEBUGTEXT_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sdtx_desc_t.allocator to override memory allocation functions" +#endif + +#include // memset +#include // fmodf +#include // for vsnprintf +#include // malloc/free + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#ifndef SOKOL_VSNPRINTF +#include +#define SOKOL_VSNPRINTF vsnprintf +#endif + +#define _sdtx_def(val, def) (((val) == 0) ? (def) : (val)) +#define _SDTX_INIT_COOKIE (0xACBAABCA) + +#define _SDTX_DEFAULT_MAX_COMMANDS (4096) +#define _SDTX_DEFAULT_CONTEXT_POOL_SIZE (8) +#define _SDTX_DEFAULT_CHAR_BUF_SIZE (4096) +#define _SDTX_DEFAULT_PRINTF_BUF_SIZE (4096) +#define _SDTX_DEFAULT_CANVAS_WIDTH (640) +#define _SDTX_DEFAULT_CANVAS_HEIGHT (480) +#define _SDTX_DEFAULT_TAB_WIDTH (4) +#define _SDTX_DEFAULT_COLOR (0xFF00FFFF) +#define _SDTX_INVALID_SLOT_INDEX (0) +#define _SDTX_SLOT_SHIFT (16) +#define _SDTX_MAX_POOL_SIZE (1<<_SDTX_SLOT_SHIFT) +#define _SDTX_SLOT_MASK (_SDTX_MAX_POOL_SIZE-1) + +/* embedded font data */ +static const uint8_t _sdtx_font_kc853[2048] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xFF, // 00 + 0x00, 0x00, 0x22, 0x72, 0x22, 0x3E, 0x00, 0x00, // 01 + 0x00, 0x00, 0x12, 0x32, 0x7E, 0x32, 0x12, 0x00, // 02 + 0x7E, 0x81, 0xB9, 0xA5, 0xB9, 0xA5, 0xB9, 0x81, // 03 + 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 04 + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 05 + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, // 06 + 0x00, 0x00, 0x3C, 0x42, 0x42, 0x7E, 0x00, 0x00, // 07 + 0x00, 0x10, 0x30, 0x7E, 0x30, 0x10, 0x00, 0x00, // 08 + 0x00, 0x08, 0x0C, 0x7E, 0x0C, 0x08, 0x00, 0x00, // 09 + 0x00, 0x10, 0x10, 0x10, 0x7C, 0x38, 0x10, 0x00, // 0A + 0x08, 0x1C, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x00, // 0B + 0x38, 0x30, 0x28, 0x08, 0x08, 0x08, 0x3E, 0x00, // 0C + 0x00, 0x00, 0x12, 0x32, 0x7E, 0x30, 0x10, 0x00, // 0D + 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, // 0E + 0x3E, 0x7C, 0x7C, 0x3E, 0x3E, 0x7C, 0xF8, 0xF8, // 0F + 0x38, 0x30, 0x28, 0x04, 0x04, 0x04, 0x04, 0x00, // 10 + 0x7F, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x08, 0x00, // 11 + 0x00, 0x08, 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x7F, // 12 + 0x7E, 0x81, 0x9D, 0xA1, 0xB9, 0x85, 0x85, 0xB9, // 13 + 0x00, 0x3C, 0x42, 0x5A, 0x5A, 0x42, 0x3C, 0x00, // 14 + 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11, // 15 + 0x00, 0x7F, 0x22, 0x72, 0x27, 0x22, 0x7F, 0x00, // 16 + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, // 17 + 0x00, 0x01, 0x09, 0x0D, 0x7F, 0x0D, 0x09, 0x01, // 18 + 0x00, 0x90, 0xB0, 0xFE, 0xB0, 0x90, 0x00, 0x00, // 19 + 0x00, 0x08, 0x7C, 0x06, 0x7C, 0x08, 0x00, 0x00, // 1A + 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, // 1B + 0x7E, 0x81, 0xA1, 0xA1, 0xA1, 0xA1, 0xBD, 0x81, // 1C + 0x7E, 0x81, 0xB9, 0xA5, 0xB9, 0xA5, 0xA5, 0x81, // 1D + 0x7E, 0x81, 0x99, 0xA1, 0xA1, 0xA1, 0x99, 0x81, // 1E + 0x00, 0x10, 0x3E, 0x60, 0x3E, 0x10, 0x00, 0x00, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, // 21 + 0x00, 0x66, 0x66, 0xCC, 0x00, 0x00, 0x00, 0x00, // 22 + 0x00, 0x36, 0x7F, 0x36, 0x36, 0x7F, 0x36, 0x00, // 23 + 0x18, 0x3E, 0x6C, 0x3E, 0x1B, 0x1B, 0x7E, 0x18, // 24 + 0x00, 0x63, 0x66, 0x0C, 0x18, 0x36, 0x66, 0x00, // 25 + 0x18, 0x24, 0x28, 0x11, 0x2A, 0x44, 0x4A, 0x31, // 26 + 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, // 27 + 0x00, 0x18, 0x30, 0x30, 0x30, 0x30, 0x18, 0x00, // 28 + 0x00, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x00, // 29 + 0x00, 0x00, 0x24, 0x18, 0x7E, 0x18, 0x24, 0x00, // 2A + 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, // 2C + 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // 2E + 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x00, 0x00, // 2F + 0x00, 0x3C, 0x6E, 0x6E, 0x76, 0x76, 0x3C, 0x00, // 30 + 0x00, 0x1C, 0x3C, 0x0C, 0x0C, 0x0C, 0x3E, 0x00, // 31 + 0x00, 0x3C, 0x66, 0x06, 0x3C, 0x60, 0x7E, 0x00, // 32 + 0x00, 0x3C, 0x66, 0x0C, 0x06, 0x66, 0x3C, 0x00, // 33 + 0x00, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x00, // 34 + 0x00, 0x7E, 0x60, 0x7C, 0x06, 0x66, 0x3C, 0x00, // 35 + 0x00, 0x3C, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, // 36 + 0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, // 37 + 0x00, 0x3C, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, // 38 + 0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00, // 39 + 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, // 3A + 0x00, 0x00, 0x18, 0x00, 0x18, 0x18, 0x30, 0x00, // 3B + 0x00, 0x00, 0x18, 0x30, 0x60, 0x30, 0x18, 0x00, // 3C + 0x00, 0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, // 3D + 0x00, 0x00, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x00, // 3E + 0x00, 0x3C, 0x66, 0x06, 0x1C, 0x18, 0x00, 0x18, // 3F + 0x3C, 0x42, 0x81, 0x35, 0x49, 0x49, 0x49, 0x36, // 40 + 0x00, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, // 41 + 0x00, 0x7C, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, // 42 + 0x00, 0x3C, 0x66, 0x60, 0x60, 0x66, 0x3C, 0x00, // 43 + 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, // 44 + 0x00, 0x7E, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00, // 45 + 0x00, 0x7E, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00, // 46 + 0x00, 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x3C, 0x00, // 47 + 0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // 48 + 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // 49 + 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, // 4A + 0x00, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x63, 0x00, // 4B + 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, // 4C + 0x00, 0x63, 0x77, 0x6B, 0x63, 0x63, 0x63, 0x00, // 4D + 0x00, 0x63, 0x73, 0x6B, 0x67, 0x63, 0x63, 0x00, // 4E + 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 4F + 0x00, 0x7C, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, // 50 + 0x00, 0x3C, 0x66, 0x66, 0x6E, 0x66, 0x3A, 0x01, // 51 + 0x00, 0x7C, 0x66, 0x7C, 0x6C, 0x66, 0x63, 0x00, // 52 + 0x00, 0x3C, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, // 53 + 0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 54 + 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 55 + 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // 56 + 0x00, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x36, 0x00, // 57 + 0x00, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x66, 0x00, // 58 + 0x00, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x00, // 59 + 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, // 5A + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 5B + 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 5C + 0x00, 0x7E, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, // 5D + 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, 0x00, 0x00, // 5E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // 5F + 0x7E, 0x81, 0x99, 0xA1, 0xA1, 0x99, 0x81, 0x7E, // 60 + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3B, 0x00, // 61 + 0x00, 0x60, 0x60, 0x78, 0x6C, 0x6C, 0x78, 0x00, // 62 + 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, // 63 + 0x00, 0x06, 0x06, 0x1E, 0x36, 0x36, 0x1E, 0x00, // 64 + 0x00, 0x00, 0x38, 0x6C, 0x7C, 0x60, 0x38, 0x00, // 65 + 0x00, 0x1E, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x00, // 66 + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x3F, 0x06, 0x3C, // 67 + 0x00, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x00, // 68 + 0x00, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, // 69 + 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x30, // 6A + 0x00, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00, // 6B + 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x00, // 6C + 0x00, 0x00, 0x36, 0x7F, 0x6B, 0x63, 0x63, 0x00, // 6D + 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, // 6E + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, // 6F + 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, // 70 + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x06, // 71 + 0x00, 0x00, 0x36, 0x38, 0x30, 0x30, 0x30, 0x00, // 72 + 0x00, 0x00, 0x1C, 0x30, 0x1C, 0x06, 0x3C, 0x00, // 73 + 0x00, 0x18, 0x18, 0x3C, 0x18, 0x18, 0x0C, 0x00, // 74 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 75 + 0x00, 0x00, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x00, // 76 + 0x00, 0x00, 0x63, 0x63, 0x6B, 0x7F, 0x36, 0x00, // 77 + 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, // 78 + 0x00, 0x00, 0x66, 0x3C, 0x18, 0x30, 0x60, 0x00, // 79 + 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00, // 7A + 0x66, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3B, 0x00, // 7B + 0x66, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, // 7C + 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 7D + 0x00, 0x38, 0x6C, 0x78, 0x6C, 0x78, 0x60, 0x60, // 7E + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, // 7F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x00, // 80 + 0xFF, 0xFF, 0xDD, 0x8D, 0xDD, 0xC1, 0xFF, 0xFF, // 81 + 0xFF, 0xFF, 0xED, 0xCD, 0x81, 0xCD, 0xED, 0xFF, // 82 + 0x81, 0x7E, 0x46, 0x5A, 0x46, 0x5A, 0x46, 0x7E, // 83 + 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, // 84 + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // 85 + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, // 86 + 0xFF, 0xFF, 0xC3, 0xBD, 0xBD, 0x81, 0xFF, 0xFF, // 87 + 0xFF, 0xEF, 0xCF, 0x81, 0xCF, 0xEF, 0xFF, 0xFF, // 88 + 0xFF, 0xF7, 0xF3, 0x81, 0xF3, 0xF7, 0xFF, 0xFF, // 89 + 0xFF, 0xEF, 0xEF, 0xEF, 0x83, 0xC7, 0xEF, 0xFF, // 8A + 0xF7, 0xE3, 0xC1, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, // 8B + 0xC7, 0xCF, 0xD7, 0xF7, 0xF7, 0xF7, 0xC1, 0xFF, // 8C + 0xFF, 0xFF, 0xED, 0xCD, 0x81, 0xCF, 0xEF, 0xFF, // 8D + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 8E + 0xC1, 0x83, 0x83, 0xC1, 0xC1, 0x83, 0x07, 0x07, // 8F + 0xC7, 0xCF, 0xD7, 0xFB, 0xFB, 0xFB, 0xFB, 0xFF, // 90 + 0x80, 0xF7, 0xE3, 0xD5, 0xF7, 0xF7, 0xF7, 0xFF, // 91 + 0xFF, 0xF7, 0xF7, 0xF7, 0xD5, 0xE3, 0xF7, 0x80, // 92 + 0x81, 0x7E, 0x62, 0x5E, 0x46, 0x7A, 0x7A, 0x46, // 93 + 0xFF, 0xC3, 0xBD, 0xA5, 0xA5, 0xBD, 0xC3, 0xFF, // 94 + 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, // 95 + 0xFF, 0x80, 0xDD, 0x8D, 0xD8, 0xDD, 0x80, 0xFF, // 96 + 0xEE, 0xDD, 0xBB, 0x77, 0xEE, 0xDD, 0xBB, 0x77, // 97 + 0xFF, 0xFE, 0xF6, 0xF2, 0x80, 0xF2, 0xF6, 0xFE, // 98 + 0xFF, 0x6F, 0x4F, 0x01, 0x4F, 0x6F, 0xFF, 0xFF, // 99 + 0xFF, 0xF7, 0x83, 0xF9, 0x83, 0xF7, 0xFF, 0xFF, // 9A + 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, // 9B + 0x81, 0x7E, 0x5E, 0x5E, 0x5E, 0x5E, 0x42, 0x7E, // 9C + 0x81, 0x7E, 0x46, 0x5A, 0x46, 0x5A, 0x5A, 0x7E, // 9D + 0x81, 0x7E, 0x66, 0x5E, 0x5E, 0x5E, 0x66, 0x7E, // 9E + 0xFF, 0xEF, 0xC1, 0x9F, 0xC1, 0xEF, 0xFF, 0xFF, // 9F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0 + 0xFF, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xE7, 0xFF, // A1 + 0xFF, 0x99, 0x99, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, // A2 + 0xFF, 0xC9, 0x80, 0xC9, 0xC9, 0x80, 0xC9, 0xFF, // A3 + 0xE7, 0xC1, 0x93, 0xC1, 0xE4, 0xE4, 0x81, 0xE7, // A4 + 0xFF, 0x9C, 0x99, 0xF3, 0xE7, 0xC9, 0x99, 0xFF, // A5 + 0xE7, 0xDB, 0xD7, 0xEE, 0xD5, 0xBB, 0xB5, 0xCE, // A6 + 0xFF, 0xE7, 0xE7, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, // A7 + 0xFF, 0xE7, 0xCF, 0xCF, 0xCF, 0xCF, 0xE7, 0xFF, // A8 + 0xFF, 0xE7, 0xF3, 0xF3, 0xF3, 0xF3, 0xE7, 0xFF, // A9 + 0xFF, 0xFF, 0xDB, 0xE7, 0x81, 0xE7, 0xDB, 0xFF, // AA + 0xFF, 0xFF, 0xE7, 0xE7, 0x81, 0xE7, 0xE7, 0xFF, // AB + 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 0xFF, // AC + 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, // AD + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, // AE + 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3F, 0xFF, 0xFF, // AF + 0xFF, 0xC3, 0x91, 0x91, 0x89, 0x89, 0xC3, 0xFF, // B0 + 0xFF, 0xE3, 0xC3, 0xF3, 0xF3, 0xF3, 0xC1, 0xFF, // B1 + 0xFF, 0xC3, 0x99, 0xF9, 0xC3, 0x9F, 0x81, 0xFF, // B2 + 0xFF, 0xC3, 0x99, 0xF3, 0xF9, 0x99, 0xC3, 0xFF, // B3 + 0xFF, 0xC3, 0x93, 0x33, 0x01, 0xF3, 0xF3, 0xFF, // B4 + 0xFF, 0x81, 0x9F, 0x83, 0xF9, 0x99, 0xC3, 0xFF, // B5 + 0xFF, 0xC3, 0x9F, 0x83, 0x99, 0x99, 0xC3, 0xFF, // B6 + 0xFF, 0x81, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF, // B7 + 0xFF, 0xC3, 0x99, 0xC3, 0x99, 0x99, 0xC3, 0xFF, // B8 + 0xFF, 0xC3, 0x99, 0x99, 0xC1, 0xF9, 0xC3, 0xFF, // B9 + 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xE7, 0xE7, 0xFF, // BA + 0xFF, 0xFF, 0xE7, 0xFF, 0xE7, 0xE7, 0xCF, 0xFF, // BB + 0xFF, 0xFF, 0xE7, 0xCF, 0x9F, 0xCF, 0xE7, 0xFF, // BC + 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xC1, 0xFF, 0xFF, // BD + 0xFF, 0xFF, 0xCF, 0xE7, 0xF3, 0xE7, 0xCF, 0xFF, // BE + 0xFF, 0xC3, 0x99, 0xF9, 0xE3, 0xE7, 0xFF, 0xE7, // BF + 0xC3, 0xBD, 0x7E, 0xCA, 0xB6, 0xB6, 0xB6, 0xC9, // C0 + 0xFF, 0xC3, 0x99, 0x99, 0x81, 0x99, 0x99, 0xFF, // C1 + 0xFF, 0x83, 0x99, 0x83, 0x99, 0x99, 0x83, 0xFF, // C2 + 0xFF, 0xC3, 0x99, 0x9F, 0x9F, 0x99, 0xC3, 0xFF, // C3 + 0xFF, 0x83, 0x99, 0x99, 0x99, 0x99, 0x83, 0xFF, // C4 + 0xFF, 0x81, 0x9F, 0x83, 0x9F, 0x9F, 0x81, 0xFF, // C5 + 0xFF, 0x81, 0x9F, 0x83, 0x9F, 0x9F, 0x9F, 0xFF, // C6 + 0xFF, 0xC3, 0x99, 0x9F, 0x91, 0x99, 0xC3, 0xFF, // C7 + 0xFF, 0x99, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, // C8 + 0xFF, 0xC3, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF, // C9 + 0xFF, 0xE1, 0xF3, 0xF3, 0xF3, 0x93, 0xC7, 0xFF, // CA + 0xFF, 0x99, 0x93, 0x87, 0x93, 0x99, 0x9C, 0xFF, // CB + 0xFF, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x81, 0xFF, // CC + 0xFF, 0x9C, 0x88, 0x94, 0x9C, 0x9C, 0x9C, 0xFF, // CD + 0xFF, 0x9C, 0x8C, 0x94, 0x98, 0x9C, 0x9C, 0xFF, // CE + 0xFF, 0xC3, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, // CF + 0xFF, 0x83, 0x99, 0x83, 0x9F, 0x9F, 0x9F, 0xFF, // D0 + 0xFF, 0xC3, 0x99, 0x99, 0x91, 0x99, 0xC5, 0xFE, // D1 + 0xFF, 0x83, 0x99, 0x83, 0x93, 0x99, 0x9C, 0xFF, // D2 + 0xFF, 0xC3, 0x9F, 0xC3, 0xF9, 0x99, 0xC3, 0xFF, // D3 + 0xFF, 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, // D4 + 0xFF, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, // D5 + 0xFF, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, // D6 + 0xFF, 0x9C, 0x9C, 0x94, 0x94, 0x80, 0xC9, 0xFF, // D7 + 0xFF, 0x99, 0xC3, 0xE7, 0xE7, 0xC3, 0x99, 0xFF, // D8 + 0xFF, 0x99, 0xC3, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, // D9 + 0xFF, 0x81, 0xF3, 0xE7, 0xCF, 0x9F, 0x81, 0xFF, // DA + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DB + 0xFF, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, // DC + 0xFF, 0x81, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, // DD + 0xFF, 0xFF, 0xFF, 0xF7, 0xE3, 0xC9, 0xFF, 0xFF, // DE + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // DF + 0x81, 0x7E, 0x66, 0x5E, 0x5E, 0x66, 0x7E, 0x81, // E0 + 0xFF, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0xC4, 0xFF, // E1 + 0xFF, 0x9F, 0x9F, 0x87, 0x93, 0x93, 0x87, 0xFF, // E2 + 0xFF, 0xFF, 0xC3, 0x99, 0x9F, 0x99, 0xC3, 0xFF, // E3 + 0xFF, 0xF9, 0xF9, 0xE1, 0xC9, 0xC9, 0xE1, 0xFF, // E4 + 0xFF, 0xFF, 0xC7, 0x93, 0x83, 0x9F, 0xC7, 0xFF, // E5 + 0xFF, 0xE1, 0xE7, 0x81, 0xE7, 0xE7, 0xE7, 0xFF, // E6 + 0xFF, 0xFF, 0xC3, 0x99, 0x99, 0xC0, 0xF9, 0xC3, // E7 + 0xFF, 0x9F, 0x9F, 0x93, 0x89, 0x99, 0x99, 0xFF, // E8 + 0xFF, 0xE7, 0xFF, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, // E9 + 0xFF, 0xE7, 0xFF, 0xC7, 0xE7, 0xE7, 0xE7, 0xCF, // EA + 0xFF, 0x9F, 0x99, 0x93, 0x87, 0x93, 0x99, 0xFF, // EB + 0xFF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xE7, 0xFF, // EC + 0xFF, 0xFF, 0xC9, 0x80, 0x94, 0x9C, 0x9C, 0xFF, // ED + 0xFF, 0xFF, 0x83, 0x99, 0x99, 0x99, 0x99, 0xFF, // EE + 0xFF, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0xC3, 0xFF, // EF + 0xFF, 0xFF, 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, // F0 + 0xFF, 0xFF, 0xC3, 0x99, 0x99, 0xC1, 0xF9, 0xF9, // F1 + 0xFF, 0xFF, 0xC9, 0xC7, 0xCF, 0xCF, 0xCF, 0xFF, // F2 + 0xFF, 0xFF, 0xE3, 0xCF, 0xE3, 0xF9, 0xC3, 0xFF, // F3 + 0xFF, 0xE7, 0xE7, 0xC3, 0xE7, 0xE7, 0xF3, 0xFF, // F4 + 0xFF, 0xFF, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, // F5 + 0xFF, 0xFF, 0x99, 0x99, 0xC3, 0xC3, 0xE7, 0xFF, // F6 + 0xFF, 0xFF, 0x9C, 0x9C, 0x94, 0x80, 0xC9, 0xFF, // F7 + 0xFF, 0xFF, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0xFF, // F8 + 0xFF, 0xFF, 0x99, 0xC3, 0xE7, 0xCF, 0x9F, 0xFF, // F9 + 0xFF, 0xFF, 0x81, 0xF3, 0xE7, 0xCF, 0x81, 0xFF, // FA + 0x99, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0xC4, 0xFF, // FB + 0x99, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0xC3, 0xFF, // FC + 0x99, 0xFF, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, // FD + 0xFF, 0xC7, 0x93, 0x87, 0x93, 0x87, 0x9F, 0x9F, // FE + 0x00, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x00, // FF +}; +static const uint8_t _sdtx_font_kc854[2048] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xFF, // 00 + 0x00, 0x00, 0x22, 0x72, 0x22, 0x3E, 0x00, 0x00, // 01 + 0x00, 0x00, 0x12, 0x32, 0x7E, 0x32, 0x12, 0x00, // 02 + 0x7E, 0x81, 0xB9, 0xA5, 0xB9, 0xA5, 0xB9, 0x81, // 03 + 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 04 + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 05 + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, // 06 + 0x00, 0x00, 0x3C, 0x42, 0x42, 0x7E, 0x00, 0x00, // 07 + 0x00, 0x10, 0x30, 0x7E, 0x30, 0x10, 0x00, 0x00, // 08 + 0x00, 0x08, 0x0C, 0x7E, 0x0C, 0x08, 0x00, 0x00, // 09 + 0x00, 0x10, 0x10, 0x10, 0x7C, 0x38, 0x10, 0x00, // 0A + 0x08, 0x1C, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x00, // 0B + 0x38, 0x30, 0x28, 0x08, 0x08, 0x08, 0x3E, 0x00, // 0C + 0x00, 0x00, 0x12, 0x32, 0x7E, 0x30, 0x10, 0x00, // 0D + 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, // 0E + 0x3E, 0x7C, 0x7C, 0x3E, 0x3E, 0x7C, 0xF8, 0xF8, // 0F + 0x38, 0x30, 0x28, 0x04, 0x04, 0x04, 0x04, 0x00, // 10 + 0x7F, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x08, 0x00, // 11 + 0x00, 0x08, 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x7F, // 12 + 0x7E, 0x81, 0x9D, 0xA1, 0xB9, 0x85, 0x85, 0xB9, // 13 + 0x00, 0x3C, 0x42, 0x5A, 0x5A, 0x42, 0x3C, 0x00, // 14 + 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11, // 15 + 0x00, 0x7F, 0x22, 0x72, 0x27, 0x22, 0x7F, 0x00, // 16 + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, // 17 + 0x00, 0x01, 0x09, 0x0D, 0x7F, 0x0D, 0x09, 0x01, // 18 + 0x00, 0x90, 0xB0, 0xFE, 0xB0, 0x90, 0x00, 0x00, // 19 + 0x00, 0x08, 0x7C, 0x06, 0x7C, 0x08, 0x00, 0x00, // 1A + 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, // 1B + 0x7E, 0x81, 0xA1, 0xA1, 0xA1, 0xA1, 0xBD, 0x81, // 1C + 0x7E, 0x81, 0xB9, 0xA5, 0xB9, 0xA5, 0xA5, 0x81, // 1D + 0x7E, 0x81, 0x99, 0xA1, 0xA1, 0xA1, 0x99, 0x81, // 1E + 0x00, 0x10, 0x3E, 0x60, 0x3E, 0x10, 0x00, 0x00, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x00, // 21 + 0x77, 0x33, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 + 0x36, 0x36, 0xFE, 0x6C, 0xFE, 0xD8, 0xD8, 0x00, // 23 + 0x18, 0x3E, 0x6C, 0x3E, 0x1B, 0x1B, 0x7E, 0x18, // 24 + 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, // 25 + 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, // 26 + 0x1C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 + 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, // 28 + 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, // 29 + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, // 2A + 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x0C, 0x18, // 2C + 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, // 2E + 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, // 2F + 0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00, // 30 + 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, // 31 + 0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00, // 32 + 0xFC, 0x18, 0x30, 0x78, 0x0C, 0xCC, 0x78, 0x00, // 33 + 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, // 34 + 0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, // 35 + 0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00, // 36 + 0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, // 37 + 0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00, // 38 + 0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00, // 39 + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, // 3A + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, // 3B + 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, // 3C + 0x00, 0x00, 0xFC, 0x00, 0xFC, 0x00, 0x00, 0x00, // 3D + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, // 3E + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, // 3F + 0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00, // 40 + 0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00, // 41 + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, // 42 + 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, // 43 + 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, // 44 + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, // 45 + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, // 46 + 0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3C, 0x00, // 47 + 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, // 48 + 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, // 49 + 0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, // 4A + 0xE6, 0x66, 0x6C, 0x70, 0x6C, 0x66, 0xE6, 0x00, // 4B + 0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, // 4C + 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, // 4D + 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, // 4E + 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, // 4F + 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, // 50 + 0x78, 0xCC, 0xCC, 0xCC, 0xDC, 0x78, 0x1C, 0x00, // 51 + 0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00, // 52 + 0x7C, 0xC6, 0xF0, 0x3C, 0x0E, 0xC6, 0x7C, 0x00, // 53 + 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, // 54 + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 55 + 0xCC, 0xCC, 0xCC, 0x78, 0x78, 0x30, 0x30, 0x00, // 56 + 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, // 57 + 0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00, // 58 + 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x78, 0x00, // 59 + 0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00, // 5A + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 5B + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 5C + 0x00, 0xFE, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, // 5D + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, // 5E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // 5F + 0x3C, 0x42, 0x99, 0xA1, 0xA1, 0x99, 0x42, 0x3C, // 60 + 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, // 61 + 0xE0, 0x60, 0x7C, 0x66, 0x66, 0x66, 0xDC, 0x00, // 62 + 0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00, // 63 + 0x1C, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 64 + 0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, // 65 + 0x38, 0x6C, 0x60, 0xF0, 0x60, 0x60, 0xF0, 0x00, // 66 + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, // 67 + 0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, // 68 + 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0xFC, 0x00, // 69 + 0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, // 6A + 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, // 6B + 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, // 6C + 0x00, 0x00, 0xCC, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, // 6D + 0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, // 6E + 0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 6F + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, // 70 + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, // 71 + 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0xF0, 0x00, // 72 + 0x00, 0x00, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x00, // 73 + 0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00, // 74 + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 75 + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, // 76 + 0x00, 0x00, 0xC6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00, // 77 + 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, // 78 + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, // 79 + 0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00, // 7A + 0x6C, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, // 7B + 0xCC, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 7C + 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 7D + 0x3C, 0x66, 0x66, 0x6C, 0x66, 0x66, 0x6C, 0xF0, // 7E + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, // 7F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x00, // 80 + 0xFF, 0xFF, 0xDD, 0x8D, 0xDD, 0xC1, 0xFF, 0xFF, // 81 + 0xFF, 0xFF, 0xED, 0xCD, 0x81, 0xCD, 0xED, 0xFF, // 82 + 0x81, 0x7E, 0x46, 0x5A, 0x46, 0x5A, 0x46, 0x7E, // 83 + 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, // 84 + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // 85 + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, // 86 + 0xFF, 0xFF, 0xC3, 0xBD, 0xBD, 0x81, 0xFF, 0xFF, // 87 + 0xFF, 0xEF, 0xCF, 0x81, 0xCF, 0xEF, 0xFF, 0xFF, // 88 + 0xFF, 0xF7, 0xF3, 0x81, 0xF3, 0xF7, 0xFF, 0xFF, // 89 + 0xFF, 0xEF, 0xEF, 0xEF, 0x83, 0xC7, 0xEF, 0xFF, // 8A + 0xF7, 0xE3, 0xC1, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, // 8B + 0xC7, 0xCF, 0xD7, 0xF7, 0xF7, 0xF7, 0xC1, 0xFF, // 8C + 0xFF, 0xFF, 0xED, 0xCD, 0x81, 0xCF, 0xEF, 0xFF, // 8D + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 8E + 0xC1, 0x83, 0x83, 0xC1, 0xC1, 0x83, 0x07, 0x07, // 8F + 0xC7, 0xCF, 0xD7, 0xFB, 0xFB, 0xFB, 0xFB, 0xFF, // 90 + 0x80, 0xF7, 0xE3, 0xD5, 0xF7, 0xF7, 0xF7, 0xFF, // 91 + 0xFF, 0xF7, 0xF7, 0xF7, 0xD5, 0xE3, 0xF7, 0x80, // 92 + 0x81, 0x7E, 0x62, 0x5E, 0x46, 0x7A, 0x7A, 0x46, // 93 + 0xFF, 0xC3, 0xBD, 0xA5, 0xA5, 0xBD, 0xC3, 0xFF, // 94 + 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, // 95 + 0xFF, 0x80, 0xDD, 0x8D, 0xD8, 0xDD, 0x80, 0xFF, // 96 + 0xEE, 0xDD, 0xBB, 0x77, 0xEE, 0xDD, 0xBB, 0x77, // 97 + 0xFF, 0xFE, 0xF6, 0xF2, 0x80, 0xF2, 0xF6, 0xFE, // 98 + 0xFF, 0x6F, 0x4F, 0x01, 0x4F, 0x6F, 0xFF, 0xFF, // 99 + 0xFF, 0xF7, 0x83, 0xF9, 0x83, 0xF7, 0xFF, 0xFF, // 9A + 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, // 9B + 0x81, 0x7E, 0x5E, 0x5E, 0x5E, 0x5E, 0x42, 0x7E, // 9C + 0x81, 0x7E, 0x46, 0x5A, 0x46, 0x5A, 0x5A, 0x7E, // 9D + 0x81, 0x7E, 0x66, 0x5E, 0x5E, 0x5E, 0x66, 0x7E, // 9E + 0xFF, 0xEF, 0xC1, 0x9F, 0xC1, 0xEF, 0xFF, 0xFF, // 9F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0 + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xFF, 0xCF, 0xFF, // A1 + 0x88, 0xCC, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A2 + 0xC9, 0xC9, 0x01, 0x93, 0x01, 0x27, 0x27, 0xFF, // A3 + 0xE7, 0xC1, 0x93, 0xC1, 0xE4, 0xE4, 0x81, 0xE7, // A4 + 0xFF, 0x39, 0x33, 0xE7, 0xCF, 0x99, 0x39, 0xFF, // A5 + 0xC7, 0x93, 0xC7, 0x89, 0x23, 0x33, 0x89, 0xFF, // A6 + 0xE3, 0xF3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A7 + 0xE7, 0xCF, 0x9F, 0x9F, 0x9F, 0xCF, 0xE7, 0xFF, // A8 + 0x9F, 0xCF, 0xE7, 0xE7, 0xE7, 0xCF, 0x9F, 0xFF, // A9 + 0xFF, 0x99, 0xC3, 0x00, 0xC3, 0x99, 0xFF, 0xFF, // AA + 0xFF, 0xCF, 0xCF, 0x03, 0xCF, 0xCF, 0xFF, 0xFF, // AB + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xF3, 0xE7, // AC + 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, // AD + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xCF, 0xFF, // AE + 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x3F, 0x7F, 0xFF, // AF + 0x83, 0x39, 0x31, 0x21, 0x09, 0x19, 0x83, 0xFF, // B0 + 0xCF, 0x8F, 0xCF, 0xCF, 0xCF, 0xCF, 0x03, 0xFF, // B1 + 0x87, 0x33, 0xF3, 0xC7, 0x9F, 0x33, 0x03, 0xFF, // B2 + 0x03, 0xE7, 0xCF, 0x87, 0xF3, 0x33, 0x87, 0xFF, // B3 + 0xE3, 0xC3, 0x93, 0x33, 0x01, 0xF3, 0xE1, 0xFF, // B4 + 0x03, 0x3F, 0x07, 0xF3, 0xF3, 0x33, 0x87, 0xFF, // B5 + 0xC7, 0x9F, 0x3F, 0x07, 0x33, 0x33, 0x87, 0xFF, // B6 + 0x03, 0x33, 0xF3, 0xE7, 0xCF, 0xCF, 0xCF, 0xFF, // B7 + 0x87, 0x33, 0x33, 0x87, 0x33, 0x33, 0x87, 0xFF, // B8 + 0x87, 0x33, 0x33, 0x83, 0xF3, 0xE7, 0x8F, 0xFF, // B9 + 0xFF, 0xFF, 0xCF, 0xCF, 0xFF, 0xCF, 0xCF, 0xFF, // BA + 0xFF, 0xFF, 0xCF, 0xCF, 0xFF, 0xCF, 0xCF, 0x9F, // BB + 0xE7, 0xCF, 0x9F, 0x3F, 0x9F, 0xCF, 0xE7, 0xFF, // BC + 0xFF, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, // BD + 0x9F, 0xCF, 0xE7, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF, // BE + 0x87, 0x33, 0xF3, 0xE7, 0xCF, 0xFF, 0xCF, 0xFF, // BF + 0x83, 0x39, 0x21, 0x21, 0x21, 0x3F, 0x87, 0xFF, // C0 + 0xCF, 0x87, 0x33, 0x33, 0x03, 0x33, 0x33, 0xFF, // C1 + 0x03, 0x99, 0x99, 0x83, 0x99, 0x99, 0x03, 0xFF, // C2 + 0xC3, 0x99, 0x3F, 0x3F, 0x3F, 0x99, 0xC3, 0xFF, // C3 + 0x07, 0x93, 0x99, 0x99, 0x99, 0x93, 0x07, 0xFF, // C4 + 0x01, 0x9D, 0x97, 0x87, 0x97, 0x9D, 0x01, 0xFF, // C5 + 0x01, 0x9D, 0x97, 0x87, 0x97, 0x9F, 0x0F, 0xFF, // C6 + 0xC3, 0x99, 0x3F, 0x3F, 0x31, 0x99, 0xC3, 0xFF, // C7 + 0x33, 0x33, 0x33, 0x03, 0x33, 0x33, 0x33, 0xFF, // C8 + 0x87, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0x87, 0xFF, // C9 + 0xE1, 0xF3, 0xF3, 0xF3, 0x33, 0x33, 0x87, 0xFF, // CA + 0x19, 0x99, 0x93, 0x8F, 0x93, 0x99, 0x19, 0xFF, // CB + 0x0F, 0x9F, 0x9F, 0x9F, 0x9D, 0x99, 0x01, 0xFF, // CC + 0x39, 0x11, 0x01, 0x29, 0x39, 0x39, 0x39, 0xFF, // CD + 0x39, 0x19, 0x09, 0x21, 0x31, 0x39, 0x39, 0xFF, // CE + 0xC7, 0x93, 0x39, 0x39, 0x39, 0x93, 0xC7, 0xFF, // CF + 0x03, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0x0F, 0xFF, // D0 + 0x87, 0x33, 0x33, 0x33, 0x23, 0x87, 0xE3, 0xFF, // D1 + 0x03, 0x99, 0x99, 0x83, 0x93, 0x99, 0x19, 0xFF, // D2 + 0x83, 0x39, 0x0F, 0xC3, 0xF1, 0x39, 0x83, 0xFF, // D3 + 0x03, 0x4B, 0xCF, 0xCF, 0xCF, 0xCF, 0x87, 0xFF, // D4 + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x87, 0xFF, // D5 + 0x33, 0x33, 0x33, 0x87, 0x87, 0xCF, 0xCF, 0xFF, // D6 + 0x39, 0x39, 0x39, 0x29, 0x01, 0x11, 0x39, 0xFF, // D7 + 0x39, 0x39, 0x93, 0xC7, 0x93, 0x39, 0x39, 0xFF, // D8 + 0x33, 0x33, 0x33, 0x87, 0xCF, 0xCF, 0x87, 0xFF, // D9 + 0x01, 0x39, 0x73, 0xE7, 0xCD, 0x99, 0x01, 0xFF, // DA + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DB + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, // DC + 0xFF, 0x01, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, // DD + 0xEF, 0xC7, 0x93, 0x39, 0xFF, 0xFF, 0xFF, 0xFF, // DE + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // DF + 0xC3, 0xBD, 0x66, 0x5E, 0x5E, 0x66, 0xBD, 0xC3, // E0 + 0xFF, 0xFF, 0x87, 0xF3, 0x83, 0x33, 0x89, 0xFF, // E1 + 0x1F, 0x9F, 0x83, 0x99, 0x99, 0x99, 0x23, 0xFF, // E2 + 0xFF, 0xFF, 0x87, 0x33, 0x3F, 0x33, 0x87, 0xFF, // E3 + 0xE3, 0xF3, 0x83, 0x33, 0x33, 0x33, 0x89, 0xFF, // E4 + 0xFF, 0xFF, 0x87, 0x33, 0x03, 0x3F, 0x87, 0xFF, // E5 + 0xC7, 0x93, 0x9F, 0x0F, 0x9F, 0x9F, 0x0F, 0xFF, // E6 + 0xFF, 0xFF, 0x89, 0x33, 0x33, 0x83, 0xF3, 0x07, // E7 + 0x1F, 0x9F, 0x93, 0x89, 0x99, 0x99, 0x19, 0xFF, // E8 + 0xCF, 0xFF, 0x8F, 0xCF, 0xCF, 0xCF, 0x03, 0xFF, // E9 + 0xF3, 0xFF, 0xE3, 0xF3, 0xF3, 0x33, 0x33, 0x87, // EA + 0x1F, 0x9F, 0x99, 0x93, 0x87, 0x93, 0x19, 0xFF, // EB + 0x8F, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0x03, 0xFF, // EC + 0xFF, 0xFF, 0x33, 0x01, 0x01, 0x29, 0x39, 0xFF, // ED + 0xFF, 0xFF, 0x07, 0x33, 0x33, 0x33, 0x33, 0xFF, // EE + 0xFF, 0xFF, 0x87, 0x33, 0x33, 0x33, 0x87, 0xFF, // EF + 0xFF, 0xFF, 0x23, 0x99, 0x99, 0x83, 0x9F, 0x0F, // F0 + 0xFF, 0xFF, 0x89, 0x33, 0x33, 0x83, 0xF3, 0xE1, // F1 + 0xFF, 0xFF, 0x23, 0x89, 0x99, 0x9F, 0x0F, 0xFF, // F2 + 0xFF, 0xFF, 0x83, 0x3F, 0x87, 0xF3, 0x07, 0xFF, // F3 + 0xEF, 0xCF, 0x83, 0xCF, 0xCF, 0xCB, 0xE7, 0xFF, // F4 + 0xFF, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x89, 0xFF, // F5 + 0xFF, 0xFF, 0x33, 0x33, 0x33, 0x87, 0xCF, 0xFF, // F6 + 0xFF, 0xFF, 0x39, 0x29, 0x01, 0x01, 0x93, 0xFF, // F7 + 0xFF, 0xFF, 0x39, 0x93, 0xC7, 0x93, 0x39, 0xFF, // F8 + 0xFF, 0xFF, 0x33, 0x33, 0x33, 0x83, 0xF3, 0x07, // F9 + 0xFF, 0xFF, 0x03, 0x67, 0xCF, 0x9B, 0x03, 0xFF, // FA + 0x93, 0xFF, 0x87, 0xF3, 0x83, 0x33, 0x89, 0xFF, // FB + 0x33, 0xFF, 0x87, 0x33, 0x33, 0x33, 0x87, 0xFF, // FC + 0x33, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x89, 0xFF, // FD + 0xC3, 0x99, 0x99, 0x93, 0x99, 0x99, 0x93, 0x0F, // FE + 0x00, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x00, // FF +}; +static const uint8_t _sdtx_font_z1013[2048] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 00 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 01 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 02 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 03 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 04 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 05 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 06 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 07 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 08 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 09 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0A + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0B + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0C + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0D + 0x00, 0x00, 0x18, 0x24, 0x24, 0x18, 0x24, 0x42, // 0E + 0xDB, 0xA5, 0x81, 0xFF, 0x24, 0x24, 0x24, 0x42, // 0F + 0x08, 0x34, 0x42, 0x81, 0x91, 0x69, 0x09, 0x31, // 10 + 0x42, 0x7E, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, // 11 + 0x18, 0x24, 0x42, 0x99, 0xBD, 0x99, 0x42, 0x24, // 12 + 0x7E, 0x42, 0x99, 0xE7, 0x00, 0x00, 0x00, 0x00, // 13 + 0x18, 0xDB, 0xC3, 0x18, 0x99, 0xE7, 0x81, 0x42, // 14 + 0x18, 0x24, 0x18, 0xC3, 0xBD, 0x81, 0x81, 0x42, // 15 + 0x24, 0x7E, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, // 16 + 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x3C, 0x7E, // 17 + 0xDB, 0xFF, 0xFF, 0xFF, 0x3C, 0x3C, 0x3C, 0x7E, // 18 + 0x08, 0x3C, 0x7E, 0xFF, 0xFF, 0x6F, 0x0F, 0x3F, // 19 + 0x7E, 0x7E, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // 1A + 0x18, 0x3C, 0x7E, 0xE7, 0xC3, 0xE7, 0x7E, 0x3C, // 1B + 0x7E, 0x7E, 0xFF, 0xE7, 0x00, 0x00, 0x00, 0x00, // 1C + 0x18, 0xDB, 0xC3, 0x18, 0x99, 0xFF, 0xFF, 0x7E, // 1D + 0x18, 0x3C, 0x18, 0xC3, 0xFF, 0xFF, 0xFF, 0x7E, // 1E + 0x3C, 0x3C, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x10, 0x00, // 21 + 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 + 0x24, 0x7E, 0x24, 0x24, 0x24, 0x7E, 0x24, 0x00, // 23 + 0x10, 0x3C, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00, // 24 + 0x60, 0x64, 0x08, 0x10, 0x20, 0x4C, 0x0C, 0x00, // 25 + 0x10, 0x28, 0x28, 0x30, 0x54, 0x48, 0x34, 0x00, // 26 + 0x10, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 + 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, // 28 + 0x20, 0x10, 0x08, 0x08, 0x08, 0x10, 0x20, 0x00, // 29 + 0x00, 0x10, 0x54, 0x38, 0x54, 0x10, 0x00, 0x00, // 2A + 0x00, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00, // 2C + 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, // 2E + 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, // 2F + 0x38, 0x44, 0x44, 0x54, 0x44, 0x44, 0x38, 0x00, // 30 + 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, // 31 + 0x38, 0x44, 0x04, 0x08, 0x10, 0x20, 0x7C, 0x00, // 32 + 0x7C, 0x08, 0x10, 0x08, 0x04, 0x44, 0x38, 0x00, // 33 + 0x08, 0x18, 0x28, 0x48, 0x7C, 0x08, 0x08, 0x00, // 34 + 0x7C, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00, // 35 + 0x18, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00, // 36 + 0x7C, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x00, // 37 + 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00, // 38 + 0x38, 0x44, 0x44, 0x3C, 0x04, 0x08, 0x30, 0x00, // 39 + 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, // 3A + 0x00, 0x00, 0x10, 0x00, 0x10, 0x10, 0x20, 0x00, // 3B + 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00, // 3C + 0x00, 0x00, 0x7C, 0x00, 0x7C, 0x00, 0x00, 0x00, // 3D + 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00, // 3E + 0x38, 0x44, 0x04, 0x08, 0x10, 0x00, 0x10, 0x00, // 3F + 0x38, 0x44, 0x5C, 0x54, 0x5C, 0x40, 0x3C, 0x00, // 40 + 0x38, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x44, 0x00, // 41 + 0x78, 0x24, 0x24, 0x38, 0x24, 0x24, 0x78, 0x00, // 42 + 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00, // 43 + 0x78, 0x24, 0x24, 0x24, 0x24, 0x24, 0x78, 0x00, // 44 + 0x7C, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7C, 0x00, // 45 + 0x7C, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00, // 46 + 0x38, 0x44, 0x40, 0x40, 0x4C, 0x44, 0x3C, 0x00, // 47 + 0x44, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x44, 0x00, // 48 + 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, // 49 + 0x1C, 0x08, 0x08, 0x08, 0x08, 0x48, 0x30, 0x00, // 4A + 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00, // 4B + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7C, 0x00, // 4C + 0x44, 0x6C, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00, // 4D + 0x44, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x44, 0x00, // 4E + 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, // 4F + 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00, // 50 + 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00, // 51 + 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00, // 52 + 0x3C, 0x40, 0x40, 0x38, 0x04, 0x04, 0x78, 0x00, // 53 + 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, // 54 + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, // 55 + 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00, // 56 + 0x44, 0x44, 0x44, 0x54, 0x54, 0x6C, 0x44, 0x00, // 57 + 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00, // 58 + 0x44, 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00, // 59 + 0x7C, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7C, 0x00, // 5A + 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, // 5B + 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, // 5C + 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, // 5D + 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, // 5E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, // 5F + 0x00, 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, // 60 + 0x00, 0x00, 0x34, 0x4C, 0x44, 0x44, 0x3A, 0x00, // 61 + 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x78, 0x00, // 62 + 0x00, 0x00, 0x38, 0x44, 0x40, 0x44, 0x38, 0x00, // 63 + 0x04, 0x04, 0x34, 0x4C, 0x44, 0x44, 0x3A, 0x00, // 64 + 0x00, 0x00, 0x38, 0x44, 0x7C, 0x40, 0x38, 0x00, // 65 + 0x08, 0x10, 0x38, 0x10, 0x10, 0x10, 0x10, 0x00, // 66 + 0x00, 0x00, 0x34, 0x4C, 0x44, 0x3C, 0x04, 0x38, // 67 + 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00, // 68 + 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x08, 0x00, // 69 + 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, // 6A + 0x40, 0x40, 0x48, 0x50, 0x70, 0x48, 0x44, 0x00, // 6B + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x00, // 6C + 0x00, 0x00, 0x68, 0x54, 0x54, 0x54, 0x54, 0x00, // 6D + 0x00, 0x00, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00, // 6E + 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // 6F + 0x00, 0x00, 0x58, 0x64, 0x44, 0x78, 0x40, 0x40, // 70 + 0x00, 0x00, 0x34, 0x4C, 0x44, 0x3C, 0x04, 0x04, // 71 + 0x00, 0x00, 0x58, 0x64, 0x40, 0x40, 0x40, 0x00, // 72 + 0x00, 0x00, 0x38, 0x40, 0x38, 0x04, 0x78, 0x00, // 73 + 0x10, 0x10, 0x38, 0x10, 0x10, 0x10, 0x08, 0x00, // 74 + 0x00, 0x00, 0x44, 0x44, 0x44, 0x4C, 0x34, 0x00, // 75 + 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00, // 76 + 0x00, 0x00, 0x54, 0x54, 0x54, 0x54, 0x28, 0x00, // 77 + 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // 78 + 0x00, 0x00, 0x44, 0x44, 0x44, 0x3C, 0x04, 0x38, // 79 + 0x00, 0x00, 0x7C, 0x08, 0x10, 0x20, 0x7C, 0x00, // 7A + 0x08, 0x10, 0x10, 0x20, 0x10, 0x10, 0x08, 0x00, // 7B + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, // 7C + 0x20, 0x10, 0x10, 0x08, 0x10, 0x10, 0x20, 0x00, // 7D + 0x00, 0x00, 0x00, 0x32, 0x4C, 0x00, 0x00, 0x00, // 7E + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 7F + 0xC0, 0x20, 0x10, 0x10, 0x10, 0x10, 0x20, 0xC0, // 80 + 0x03, 0x04, 0x08, 0x08, 0x08, 0x08, 0x04, 0x03, // 81 + 0x81, 0x81, 0x42, 0x3C, 0x00, 0x00, 0x00, 0x00, // 82 + 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x81, 0x81, // 83 + 0x10, 0x10, 0x20, 0xC0, 0x00, 0x00, 0x00, 0x00, // 84 + 0x08, 0x08, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, // 85 + 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x08, 0x08, // 86 + 0x00, 0x00, 0x00, 0x00, 0xC0, 0x20, 0x10, 0x10, // 87 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFF, // 88 + 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 89 + 0x00, 0x10, 0x28, 0x44, 0x82, 0x44, 0x28, 0x10, // 8A + 0xFF, 0xEF, 0xC7, 0x83, 0x01, 0x83, 0xC7, 0xEF, // 8B + 0x3C, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3C, // 8C + 0xC3, 0x81, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC3, // 8D + 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, // 8E + 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, // 8F + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, // 90 + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, // 91 + 0x00, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x30, 0xC0, // 92 + 0x03, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, // 93 + 0x03, 0x0C, 0x30, 0xC0, 0xC0, 0x30, 0x0C, 0x03, // 94 + 0x00, 0x00, 0x00, 0x00, 0xC0, 0x30, 0x0C, 0x03, // 95 + 0xC0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, // 96 + 0xC0, 0x30, 0x0C, 0x03, 0x03, 0x0C, 0x30, 0xC0, // 97 + 0x10, 0x10, 0x20, 0x20, 0x40, 0x40, 0x80, 0x80, // 98 + 0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x08, 0x08, // 99 + 0x81, 0x81, 0x42, 0x42, 0x24, 0x24, 0x18, 0x18, // 9A + 0x80, 0x80, 0x40, 0x40, 0x20, 0x20, 0x10, 0x10, // 9B + 0x08, 0x08, 0x04, 0x04, 0x02, 0x02, 0x01, 0x01, // 9C + 0x18, 0x18, 0x24, 0x24, 0x42, 0x42, 0x81, 0x81, // 9D + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9E + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 9F + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, // A0 + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // A1 + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00, // A2 + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18, // A3 + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, // A4 + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18, // A5 + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, // A6 + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00, // A7 + 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, // A8 + 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, // A9 + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00, // AA + 0x80, 0x80, 0x80, 0x40, 0x40, 0x20, 0x18, 0x07, // AB + 0x01, 0x01, 0x01, 0x02, 0x02, 0x04, 0x18, 0xE0, // AC + 0xE0, 0x18, 0x04, 0x02, 0x02, 0x01, 0x01, 0x01, // AD + 0x07, 0x18, 0x20, 0x40, 0x40, 0x80, 0x80, 0x80, // AE + 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81, // AF + 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, // B0 + 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, // B1 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, // B2 + 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, // B3 + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // B4 + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // B5 + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // B6 + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // B7 + 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, // B8 + 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, // B9 + 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, // BA + 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, // BB + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, // BC + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, // BD + 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF, // BE + 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, // BF + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // C0 + 0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // C1 + 0xFF, 0x80, 0x80, 0x9C, 0x9C, 0x9C, 0x80, 0x80, // C2 + 0xFF, 0xFF, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xFF, // C3 + 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x3C, 0x7E, 0xFF, // C4 + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, // C5 + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // C6 + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, // C7 + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF, // C8 + 0x00, 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, // C9 + 0x38, 0x10, 0x92, 0xFE, 0x92, 0x10, 0x38, 0x7C, // CA + 0x00, 0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, // CB + 0x10, 0x38, 0x7C, 0xFE, 0xFE, 0x7C, 0x10, 0x7C, // CC + 0xE7, 0xE7, 0x42, 0xFF, 0xFF, 0x42, 0xE7, 0xE7, // CD + 0xDB, 0xFF, 0xDB, 0x18, 0x18, 0xDB, 0xFF, 0xDB, // CE + 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, // CF + 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D0 + 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D1 + 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D2 + 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D3 + 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, // D4 + 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, // D5 + 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, // D6 + 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, // D7 + 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, // D8 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, // D9 + 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, // DA + 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, // DB + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, // DC + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, // DD + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, // DE + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, // DF + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, // E0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, // E1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, // E2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFC, // E3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, // E4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, // E5 + 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, // E6 + 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // E7 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // E8 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, // E9 + 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, // EA + 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // EB + 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // EC + 0xFC, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ED + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // EE + 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // EF + 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // F0 + 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // F1 + 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, // F2 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, // F3 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // F4 + 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // F5 + 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, // F6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, // F7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // F8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, // F9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // FA + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // FB + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // FC + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // FD + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // FE + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // FF +}; +static const uint8_t _sdtx_font_cpc[2048] = { + 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, // 00 + 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // 01 + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, // 02 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, // 03 + 0x0C, 0x18, 0x30, 0x7E, 0x0C, 0x18, 0x30, 0x00, // 04 + 0xFF, 0xC3, 0xE7, 0xDB, 0xDB, 0xE7, 0xC3, 0xFF, // 05 + 0x00, 0x01, 0x03, 0x06, 0xCC, 0x78, 0x30, 0x00, // 06 + 0x3C, 0x66, 0xC3, 0xC3, 0xFF, 0x24, 0xE7, 0x00, // 07 + 0x00, 0x00, 0x30, 0x60, 0xFF, 0x60, 0x30, 0x00, // 08 + 0x00, 0x00, 0x0C, 0x06, 0xFF, 0x06, 0x0C, 0x00, // 09 + 0x18, 0x18, 0x18, 0x18, 0xDB, 0x7E, 0x3C, 0x18, // 0A + 0x18, 0x3C, 0x7E, 0xDB, 0x18, 0x18, 0x18, 0x18, // 0B + 0x18, 0x5A, 0x3C, 0x99, 0xDB, 0x7E, 0x3C, 0x18, // 0C + 0x00, 0x03, 0x33, 0x63, 0xFE, 0x60, 0x30, 0x00, // 0D + 0x3C, 0x66, 0xFF, 0xDB, 0xDB, 0xFF, 0x66, 0x3C, // 0E + 0x3C, 0x66, 0xC3, 0xDB, 0xDB, 0xC3, 0x66, 0x3C, // 0F + 0xFF, 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, // 10 + 0x3C, 0x7E, 0xDB, 0xDB, 0xDF, 0xC3, 0x66, 0x3C, // 11 + 0x3C, 0x66, 0xC3, 0xDF, 0xDB, 0xDB, 0x7E, 0x3C, // 12 + 0x3C, 0x66, 0xC3, 0xFB, 0xDB, 0xDB, 0x7E, 0x3C, // 13 + 0x3C, 0x7E, 0xDB, 0xDB, 0xFB, 0xC3, 0x66, 0x3C, // 14 + 0x00, 0x01, 0x33, 0x1E, 0xCE, 0x7B, 0x31, 0x00, // 15 + 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0xE7, // 16 + 0x03, 0x03, 0x03, 0xFF, 0x03, 0x03, 0x03, 0x00, // 17 + 0xFF, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x66, 0xFF, // 18 + 0x18, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x18, // 19 + 0x3C, 0x66, 0x66, 0x30, 0x18, 0x00, 0x18, 0x00, // 1A + 0x3C, 0x66, 0xC3, 0xFF, 0xC3, 0xC3, 0x66, 0x3C, // 1B + 0xFF, 0xDB, 0xDB, 0xDB, 0xFB, 0xC3, 0xC3, 0xFF, // 1C + 0xFF, 0xC3, 0xC3, 0xFB, 0xDB, 0xDB, 0xDB, 0xFF, // 1D + 0xFF, 0xC3, 0xC3, 0xDF, 0xDB, 0xDB, 0xDB, 0xFF, // 1E + 0xFF, 0xDB, 0xDB, 0xDB, 0xDF, 0xC3, 0xC3, 0xFF, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, // 21 + 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 + 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, // 23 + 0x18, 0x3E, 0x58, 0x3C, 0x1A, 0x7C, 0x18, 0x00, // 24 + 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, // 25 + 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, // 26 + 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 + 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, // 28 + 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, // 29 + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, // 2A + 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, // 2C + 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // 2E + 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, // 2F + 0x7C, 0xC6, 0xCE, 0xD6, 0xE6, 0xC6, 0x7C, 0x00, // 30 + 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, // 31 + 0x3C, 0x66, 0x06, 0x3C, 0x60, 0x66, 0x7E, 0x00, // 32 + 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, // 33 + 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, // 34 + 0x7E, 0x62, 0x60, 0x7C, 0x06, 0x66, 0x3C, 0x00, // 35 + 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, // 36 + 0x7E, 0x66, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x00, // 37 + 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, // 38 + 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, // 39 + 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, // 3A + 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30, // 3B + 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00, // 3C + 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, // 3D + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, // 3E + 0x3C, 0x66, 0x66, 0x0C, 0x18, 0x00, 0x18, 0x00, // 3F + 0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x7C, 0x00, // 40 + 0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, // 41 + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, // 42 + 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, // 43 + 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, // 44 + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, // 45 + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, // 46 + 0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3E, 0x00, // 47 + 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // 48 + 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, // 49 + 0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, // 4A + 0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, // 4B + 0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, // 4C + 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, // 4D + 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, // 4E + 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, // 4F + 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, // 50 + 0x38, 0x6C, 0xC6, 0xC6, 0xDA, 0xCC, 0x76, 0x00, // 51 + 0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00, // 52 + 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, // 53 + 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // 54 + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 55 + 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // 56 + 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, // 57 + 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0xC6, 0x00, // 58 + 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x00, // 59 + 0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00, // 5A + 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, // 5B + 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, // 5C + 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, // 5D + 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, // 5E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // 5F + 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, // 60 + 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, // 61 + 0xE0, 0x60, 0x7C, 0x66, 0x66, 0x66, 0xDC, 0x00, // 62 + 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, // 63 + 0x1C, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, // 64 + 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, // 65 + 0x1C, 0x36, 0x30, 0x78, 0x30, 0x30, 0x78, 0x00, // 66 + 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x7C, // 67 + 0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, // 68 + 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, // 69 + 0x06, 0x00, 0x0E, 0x06, 0x06, 0x66, 0x66, 0x3C, // 6A + 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, // 6B + 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // 6C + 0x00, 0x00, 0x6C, 0xFE, 0xD6, 0xD6, 0xC6, 0x00, // 6D + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x00, // 6E + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, // 6F + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, // 70 + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, // 71 + 0x00, 0x00, 0xDC, 0x76, 0x60, 0x60, 0xF0, 0x00, // 72 + 0x00, 0x00, 0x3C, 0x60, 0x3C, 0x06, 0x7C, 0x00, // 73 + 0x30, 0x30, 0x7C, 0x30, 0x30, 0x36, 0x1C, 0x00, // 74 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, // 75 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // 76 + 0x00, 0x00, 0xC6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00, // 77 + 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, // 78 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x7C, // 79 + 0x00, 0x00, 0x7E, 0x4C, 0x18, 0x32, 0x7E, 0x00, // 7A + 0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00, // 7B + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 7C + 0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00, // 7D + 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7E + 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, // 7F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 80 + 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, // 81 + 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, // 82 + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // 83 + 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, // 84 + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // 85 + 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, // 86 + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, // 87 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, // 88 + 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, // 89 + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // 8A + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, // 8B + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // 8C + 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, // 8D + 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, // 8E + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 8F + 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 90 + 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 91 + 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x00, 0x00, 0x00, // 92 + 0x18, 0x18, 0x18, 0x1F, 0x0F, 0x00, 0x00, 0x00, // 93 + 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, // 94 + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 95 + 0x00, 0x00, 0x00, 0x0F, 0x1F, 0x18, 0x18, 0x18, // 96 + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18, // 97 + 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, // 98 + 0x18, 0x18, 0x18, 0xF8, 0xF0, 0x00, 0x00, 0x00, // 99 + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, // 9A + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00, // 9B + 0x00, 0x00, 0x00, 0xF0, 0xF8, 0x18, 0x18, 0x18, // 9C + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18, // 9D + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, // 9E + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, // 9F + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, // A0 + 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, // A1 + 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A2 + 0x3C, 0x66, 0x60, 0xF8, 0x60, 0x66, 0xFE, 0x00, // A3 + 0x38, 0x44, 0xBA, 0xA2, 0xBA, 0x44, 0x38, 0x00, // A4 + 0x7E, 0xF4, 0xF4, 0x74, 0x34, 0x34, 0x34, 0x00, // A5 + 0x1E, 0x30, 0x38, 0x6C, 0x38, 0x18, 0xF0, 0x00, // A6 + 0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, // A7 + 0x40, 0xC0, 0x44, 0x4C, 0x54, 0x1E, 0x04, 0x00, // A8 + 0x40, 0xC0, 0x4C, 0x52, 0x44, 0x08, 0x1E, 0x00, // A9 + 0xE0, 0x10, 0x62, 0x16, 0xEA, 0x0F, 0x02, 0x00, // AA + 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x7E, 0x00, // AB + 0x18, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x18, 0x00, // AC + 0x00, 0x00, 0x00, 0x7E, 0x06, 0x06, 0x00, 0x00, // AD + 0x18, 0x00, 0x18, 0x30, 0x66, 0x66, 0x3C, 0x00, // AE + 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // AF + 0x00, 0x00, 0x73, 0xDE, 0xCC, 0xDE, 0x73, 0x00, // B0 + 0x7C, 0xC6, 0xC6, 0xFC, 0xC6, 0xC6, 0xF8, 0xC0, // B1 + 0x00, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, // B2 + 0x3C, 0x60, 0x60, 0x3C, 0x66, 0x66, 0x3C, 0x00, // B3 + 0x00, 0x00, 0x1E, 0x30, 0x7C, 0x30, 0x1E, 0x00, // B4 + 0x38, 0x6C, 0xC6, 0xFE, 0xC6, 0x6C, 0x38, 0x00, // B5 + 0x00, 0xC0, 0x60, 0x30, 0x38, 0x6C, 0xC6, 0x00, // B6 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, // B7 + 0x00, 0x00, 0x00, 0xFE, 0x6C, 0x6C, 0x6C, 0x00, // B8 + 0x00, 0x00, 0x00, 0x7E, 0xD8, 0xD8, 0x70, 0x00, // B9 + 0x03, 0x06, 0x0C, 0x3C, 0x66, 0x3C, 0x60, 0xC0, // BA + 0x03, 0x06, 0x0C, 0x66, 0x66, 0x3C, 0x60, 0xC0, // BB + 0x00, 0xE6, 0x3C, 0x18, 0x38, 0x6C, 0xC7, 0x00, // BC + 0x00, 0x00, 0x66, 0xC3, 0xDB, 0xDB, 0x7E, 0x00, // BD + 0xFE, 0xC6, 0x60, 0x30, 0x60, 0xC6, 0xFE, 0x00, // BE + 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x6C, 0xEE, 0x00, // BF + 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, // C0 + 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00, 0x00, 0x00, // C1 + 0x00, 0x00, 0x00, 0x01, 0x03, 0x06, 0x0C, 0x18, // C2 + 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x18, // C3 + 0x18, 0x3C, 0x66, 0xC3, 0x81, 0x00, 0x00, 0x00, // C4 + 0x18, 0x0C, 0x06, 0x03, 0x03, 0x06, 0x0C, 0x18, // C5 + 0x00, 0x00, 0x00, 0x81, 0xC3, 0x66, 0x3C, 0x18, // C6 + 0x18, 0x30, 0x60, 0xC0, 0xC0, 0x60, 0x30, 0x18, // C7 + 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, // C8 + 0x18, 0x0C, 0x06, 0x83, 0xC1, 0x60, 0x30, 0x18, // C9 + 0x18, 0x3C, 0x66, 0xC3, 0xC3, 0x66, 0x3C, 0x18, // CA + 0xC3, 0xE7, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, // CB + 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xC0, // CC + 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, // CD + 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, // CE + 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, // CF + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D0 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // D1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, // D2 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // D3 + 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, // D4 + 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, // D5 + 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF, // D6 + 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, // D7 + 0xAA, 0x55, 0xAA, 0x55, 0x00, 0x00, 0x00, 0x00, // D8 + 0x0A, 0x05, 0x0A, 0x05, 0x0A, 0x05, 0x0A, 0x05, // D9 + 0x00, 0x00, 0x00, 0x00, 0xAA, 0x55, 0xAA, 0x55, // DA + 0xA0, 0x50, 0xA0, 0x50, 0xA0, 0x50, 0xA0, 0x50, // DB + 0xAA, 0x54, 0xA8, 0x50, 0xA0, 0x40, 0x80, 0x00, // DC + 0xAA, 0x55, 0x2A, 0x15, 0x0A, 0x05, 0x02, 0x01, // DD + 0x01, 0x02, 0x05, 0x0A, 0x15, 0x2A, 0x55, 0xAA, // DE + 0x00, 0x80, 0x40, 0xA0, 0x50, 0xA8, 0x54, 0xAA, // DF + 0x7E, 0xFF, 0x99, 0xFF, 0xBD, 0xC3, 0xFF, 0x7E, // E0 + 0x7E, 0xFF, 0x99, 0xFF, 0xC3, 0xBD, 0xFF, 0x7E, // E1 + 0x38, 0x38, 0xFE, 0xFE, 0xFE, 0x10, 0x38, 0x00, // E2 + 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, // E3 + 0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, // E4 + 0x10, 0x38, 0x7C, 0xFE, 0xFE, 0x10, 0x38, 0x00, // E5 + 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0x66, 0x3C, 0x00, // E6 + 0x00, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x00, // E7 + 0x00, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x00, // E8 + 0x00, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x00, // E9 + 0x0F, 0x07, 0x0D, 0x78, 0xCC, 0xCC, 0xCC, 0x78, // EA + 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, // EB + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x7C, 0x38, // EC + 0x18, 0x1C, 0x1E, 0x1B, 0x18, 0x78, 0xF8, 0x70, // ED + 0x99, 0x5A, 0x24, 0xC3, 0xC3, 0x24, 0x5A, 0x99, // EE + 0x10, 0x38, 0x38, 0x38, 0x38, 0x38, 0x7C, 0xD6, // EF + 0x18, 0x3C, 0x7E, 0xFF, 0x18, 0x18, 0x18, 0x18, // F0 + 0x18, 0x18, 0x18, 0x18, 0xFF, 0x7E, 0x3C, 0x18, // F1 + 0x10, 0x30, 0x70, 0xFF, 0xFF, 0x70, 0x30, 0x10, // F2 + 0x08, 0x0C, 0x0E, 0xFF, 0xFF, 0x0E, 0x0C, 0x08, // F3 + 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x00, // F4 + 0x00, 0x00, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00, // F5 + 0x80, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0x80, 0x00, // F6 + 0x02, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x02, 0x00, // F7 + 0x38, 0x38, 0x92, 0x7C, 0x10, 0x28, 0x28, 0x28, // F8 + 0x38, 0x38, 0x10, 0xFE, 0x10, 0x28, 0x44, 0x82, // F9 + 0x38, 0x38, 0x12, 0x7C, 0x90, 0x28, 0x24, 0x22, // FA + 0x38, 0x38, 0x90, 0x7C, 0x12, 0x28, 0x48, 0x88, // FB + 0x00, 0x3C, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x00, // FC + 0x3C, 0xFF, 0xFF, 0x18, 0x0C, 0x18, 0x30, 0x18, // FD + 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, // FE + 0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00, // FF +}; +static const uint8_t _sdtx_font_c64[2048] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 00 + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // 01 + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // 02 + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 03 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // 04 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // 05 + 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, // 06 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 07 + 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0x33, 0x33, // 08 + 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, // 09 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 0A + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18, // 0B + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, // 0C + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00, // 0D + 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, // 0E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, // 0F + 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, // 10 + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00, // 11 + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, // 12 + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18, // 13 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // 14 + 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, // 15 + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, // 16 + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 17 + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // 18 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // 19 + 0x01, 0x03, 0x06, 0x6C, 0x78, 0x70, 0x60, 0x00, // 1A + 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, // 1B + 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, // 1C + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00, // 1D + 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, // 1E + 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00, // 21 + 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 + 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, 0x00, // 23 + 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, // 24 + 0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00, // 25 + 0x3C, 0x66, 0x3C, 0x38, 0x67, 0x66, 0x3F, 0x00, // 26 + 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 + 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, // 28 + 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, // 29 + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, // 2A + 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, // 2C + 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // 2E + 0x00, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, // 2F + 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00, // 30 + 0x18, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7E, 0x00, // 31 + 0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E, 0x00, // 32 + 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, // 33 + 0x06, 0x0E, 0x1E, 0x66, 0x7F, 0x06, 0x06, 0x00, // 34 + 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00, // 35 + 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, // 36 + 0x7E, 0x66, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x00, // 37 + 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, // 38 + 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, // 39 + 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, // 3A + 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, // 3B + 0x0E, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0E, 0x00, // 3C + 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, // 3D + 0x70, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x70, 0x00, // 3E + 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00, // 3F + 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00, // 40 + 0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // 41 + 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, // 42 + 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, // 43 + 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, // 44 + 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00, // 45 + 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x00, // 46 + 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00, // 47 + 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // 48 + 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // 49 + 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, // 4A + 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, // 4B + 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, // 4C + 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00, // 4D + 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, // 4E + 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 4F + 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, // 50 + 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E, 0x00, // 51 + 0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00, // 52 + 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, // 53 + 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 54 + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // 55 + 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // 56 + 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00, // 57 + 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, // 58 + 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, // 59 + 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, // 5A + 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, // 5B + 0x0C, 0x12, 0x30, 0x7C, 0x30, 0x62, 0xFC, 0x00, // 5C + 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, // 5D + 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, // 5E + 0x00, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00, // 5F + 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00, // 60 + 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00, // 61 + 0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, // 62 + 0x00, 0x00, 0x3C, 0x60, 0x60, 0x60, 0x3C, 0x00, // 63 + 0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3E, 0x00, // 64 + 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, // 65 + 0x00, 0x0E, 0x18, 0x3E, 0x18, 0x18, 0x18, 0x00, // 66 + 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x7C, // 67 + 0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x00, // 68 + 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3C, 0x00, // 69 + 0x00, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3C, // 6A + 0x00, 0x60, 0x60, 0x6C, 0x78, 0x6C, 0x66, 0x00, // 6B + 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // 6C + 0x00, 0x00, 0x66, 0x7F, 0x7F, 0x6B, 0x63, 0x00, // 6D + 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, // 6E + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, // 6F + 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, // 70 + 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06, // 71 + 0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00, // 72 + 0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x00, // 73 + 0x00, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x0E, 0x00, // 74 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, // 75 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // 76 + 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x3E, 0x36, 0x00, // 77 + 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, // 78 + 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x0C, 0x78, // 79 + 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00, // 7A + 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, // 7B + 0x0C, 0x12, 0x30, 0x7C, 0x30, 0x62, 0xFC, 0x00, // 7C + 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, // 7D + 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, // 7E + 0x00, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00, // 7F + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, // 80 + 0x08, 0x1C, 0x3E, 0x7F, 0x7F, 0x1C, 0x3E, 0x00, // 81 + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 82 + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, // 83 + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // 84 + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // 85 + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, // 86 + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, // 87 + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, // 88 + 0x00, 0x00, 0x00, 0xE0, 0xF0, 0x38, 0x18, 0x18, // 89 + 0x18, 0x18, 0x1C, 0x0F, 0x07, 0x00, 0x00, 0x00, // 8A + 0x18, 0x18, 0x38, 0xF0, 0xE0, 0x00, 0x00, 0x00, // 8B + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0xFF, // 8C + 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, // 8D + 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xC0, // 8E + 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // 8F + 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 90 + 0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00, // 91 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, // 92 + 0x36, 0x7F, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00, // 93 + 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, // 94 + 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1C, 0x18, 0x18, // 95 + 0xC3, 0xE7, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, // 96 + 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x3C, 0x00, // 97 + 0x18, 0x18, 0x66, 0x66, 0x18, 0x18, 0x3C, 0x00, // 98 + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, // 99 + 0x08, 0x1C, 0x3E, 0x7F, 0x3E, 0x1C, 0x08, 0x00, // 9A + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, // 9B + 0xC0, 0xC0, 0x30, 0x30, 0xC0, 0xC0, 0x30, 0x30, // 9C + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 9D + 0x00, 0x00, 0x03, 0x3E, 0x76, 0x36, 0x36, 0x00, // 9E + 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, // 9F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A0 + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // A1 + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // A2 + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // A4 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // A5 + 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, // A6 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // A7 + 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0x33, 0x33, // A8 + 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, // A9 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // AA + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18, // AB + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, // AC + 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00, // AD + 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, // AE + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, // AF + 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, // B0 + 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00, // B1 + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, // B2 + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18, // B3 + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // B4 + 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, // B5 + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, // B6 + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B7 + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // B8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // B9 + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xFF, // BA + 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, // BB + 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, // BC + 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00, // BD + 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, // BE + 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, // BF + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // C0 + 0xF7, 0xE3, 0xC1, 0x80, 0x80, 0xE3, 0xC1, 0xFF, // C1 + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, // C2 + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // C3 + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // C4 + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // C5 + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, // C6 + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, // C7 + 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, // C8 + 0xFF, 0xFF, 0xFF, 0x1F, 0x0F, 0xC7, 0xE7, 0xE7, // C9 + 0xE7, 0xE7, 0xE3, 0xF0, 0xF8, 0xFF, 0xFF, 0xFF, // CA + 0xE7, 0xE7, 0xC7, 0x0F, 0x1F, 0xFF, 0xFF, 0xFF, // CB + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, // CC + 0x3F, 0x1F, 0x8F, 0xC7, 0xE3, 0xF1, 0xF8, 0xFC, // CD + 0xFC, 0xF8, 0xF1, 0xE3, 0xC7, 0x8F, 0x1F, 0x3F, // CE + 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, // CF + 0x00, 0x00, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, // D0 + 0xFF, 0xC3, 0x81, 0x81, 0x81, 0x81, 0xC3, 0xFF, // D1 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, // D2 + 0xC9, 0x80, 0x80, 0x80, 0xC1, 0xE3, 0xF7, 0xFF, // D3 + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, // D4 + 0xFF, 0xFF, 0xFF, 0xF8, 0xF0, 0xE3, 0xE7, 0xE7, // D5 + 0x3C, 0x18, 0x81, 0xC3, 0xC3, 0x81, 0x18, 0x3C, // D6 + 0xFF, 0xC3, 0x81, 0x99, 0x99, 0x81, 0xC3, 0xFF, // D7 + 0xE7, 0xE7, 0x99, 0x99, 0xE7, 0xE7, 0xC3, 0xFF, // D8 + 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, // D9 + 0xF7, 0xE3, 0xC1, 0x80, 0xC1, 0xE3, 0xF7, 0xFF, // DA + 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xE7, 0xE7, 0xE7, // DB + 0x3F, 0x3F, 0xCF, 0xCF, 0x3F, 0x3F, 0xCF, 0xCF, // DC + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, // DD + 0xFF, 0xFF, 0xFC, 0xC1, 0x89, 0xC9, 0xC9, 0xFF, // DE + 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, // DF + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // E0 + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // E1 + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // E2 + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // E3 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // E4 + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, // E5 + 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, // E6 + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, // E7 + 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0x33, 0xCC, 0xCC, // E8 + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, // E9 + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, // EA + 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, // EB + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, // EC + 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xFF, 0xFF, 0xFF, // ED + 0xFF, 0xFF, 0xFF, 0x07, 0x07, 0xE7, 0xE7, 0xE7, // EE + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, // EF + 0xFF, 0xFF, 0xFF, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, // F0 + 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // F1 + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0xE7, 0xE7, // F2 + 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xE7, 0xE7, 0xE7, // F3 + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, // F4 + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, // F5 + 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // F6 + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // F7 + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // F8 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, // F9 + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0x00, 0x00, // FA + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, // FB + 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, // FC + 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xFF, 0xFF, 0xFF, // FD + 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, // FE + 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, // FF +}; +static const uint8_t _sdtx_font_oric[2048] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 01 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 02 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 03 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 04 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 05 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 06 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 07 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 08 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 09 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0A + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0C + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 17 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 18 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 19 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1A + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1C + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20 + 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, // 21 + 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, // 22 + 0x14, 0x14, 0x3E, 0x14, 0x3E, 0x14, 0x14, 0x00, // 23 + 0x08, 0x1E, 0x28, 0x1C, 0x0A, 0x3C, 0x08, 0x00, // 24 + 0x30, 0x32, 0x04, 0x08, 0x10, 0x26, 0x06, 0x00, // 25 + 0x10, 0x28, 0x28, 0x10, 0x2A, 0x24, 0x1A, 0x00, // 26 + 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // 27 + 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, // 28 + 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, // 29 + 0x08, 0x2A, 0x1C, 0x08, 0x1C, 0x2A, 0x08, 0x00, // 2A + 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, // 2B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x10, // 2C + 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, // 2D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // 2E + 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, // 2F + 0x1C, 0x22, 0x26, 0x2A, 0x32, 0x22, 0x1C, 0x00, // 30 + 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, // 31 + 0x1C, 0x22, 0x02, 0x04, 0x08, 0x10, 0x3E, 0x00, // 32 + 0x3E, 0x02, 0x04, 0x0C, 0x02, 0x22, 0x1C, 0x00, // 33 + 0x04, 0x0C, 0x14, 0x24, 0x3E, 0x04, 0x04, 0x00, // 34 + 0x3E, 0x20, 0x3C, 0x02, 0x02, 0x22, 0x1C, 0x00, // 35 + 0x0C, 0x10, 0x20, 0x3C, 0x22, 0x22, 0x1C, 0x00, // 36 + 0x3E, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, 0x00, // 37 + 0x1C, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x1C, 0x00, // 38 + 0x1C, 0x22, 0x22, 0x1E, 0x02, 0x04, 0x18, 0x00, // 39 + 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, // 3A + 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x08, 0x10, // 3B + 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, // 3C + 0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, // 3D + 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, // 3E + 0x1C, 0x22, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, // 3F + 0x1C, 0x22, 0x2A, 0x2E, 0x2C, 0x20, 0x1E, 0x00, // 40 + 0x08, 0x14, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x00, // 41 + 0x3C, 0x22, 0x22, 0x3C, 0x22, 0x22, 0x3C, 0x00, // 42 + 0x1C, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, // 43 + 0x3C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3C, 0x00, // 44 + 0x3E, 0x20, 0x20, 0x3C, 0x20, 0x20, 0x3E, 0x00, // 45 + 0x3E, 0x20, 0x20, 0x3C, 0x20, 0x20, 0x20, 0x00, // 46 + 0x1E, 0x20, 0x20, 0x20, 0x26, 0x22, 0x1E, 0x00, // 47 + 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, // 48 + 0x1C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, // 49 + 0x02, 0x02, 0x02, 0x02, 0x02, 0x22, 0x1C, 0x00, // 4A + 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, // 4B + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3E, 0x00, // 4C + 0x22, 0x36, 0x2A, 0x2A, 0x22, 0x22, 0x22, 0x00, // 4D + 0x22, 0x22, 0x32, 0x2A, 0x26, 0x22, 0x22, 0x00, // 4E + 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, // 4F + 0x3C, 0x22, 0x22, 0x3C, 0x20, 0x20, 0x20, 0x00, // 50 + 0x1C, 0x22, 0x22, 0x22, 0x2A, 0x24, 0x1A, 0x00, // 51 + 0x3C, 0x22, 0x22, 0x3C, 0x28, 0x24, 0x22, 0x00, // 52 + 0x1C, 0x22, 0x20, 0x1C, 0x02, 0x22, 0x1C, 0x00, // 53 + 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // 54 + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, // 55 + 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00, // 56 + 0x22, 0x22, 0x22, 0x2A, 0x2A, 0x36, 0x22, 0x00, // 57 + 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, // 58 + 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, // 59 + 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, // 5A + 0x1E, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1E, 0x00, // 5B + 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, // 5C + 0x3C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x3C, 0x00, // 5D + 0x08, 0x14, 0x2A, 0x08, 0x08, 0x08, 0x08, 0x00, // 5E + 0x0E, 0x11, 0x3C, 0x10, 0x3C, 0x11, 0x0E, 0x00, // 5F + 0x0C, 0x12, 0x2D, 0x29, 0x29, 0x2D, 0x12, 0x0C, // 60 + 0x00, 0x00, 0x1C, 0x02, 0x1E, 0x22, 0x1E, 0x00, // 61 + 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, // 62 + 0x00, 0x00, 0x1E, 0x20, 0x20, 0x20, 0x1E, 0x00, // 63 + 0x02, 0x02, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, // 64 + 0x00, 0x00, 0x1C, 0x22, 0x3E, 0x20, 0x1E, 0x00, // 65 + 0x0C, 0x12, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x00, // 66 + 0x00, 0x00, 0x1C, 0x22, 0x22, 0x1E, 0x02, 0x1C, // 67 + 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x00, // 68 + 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x1C, 0x00, // 69 + 0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x24, 0x18, // 6A + 0x20, 0x20, 0x22, 0x24, 0x38, 0x24, 0x22, 0x00, // 6B + 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, // 6C + 0x00, 0x00, 0x36, 0x2A, 0x2A, 0x2A, 0x22, 0x00, // 6D + 0x00, 0x00, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x00, // 6E + 0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x00, // 6F + 0x00, 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x20, 0x20, // 70 + 0x00, 0x00, 0x1E, 0x22, 0x22, 0x1E, 0x02, 0x02, // 71 + 0x00, 0x00, 0x2E, 0x30, 0x20, 0x20, 0x20, 0x00, // 72 + 0x00, 0x00, 0x1E, 0x20, 0x1C, 0x02, 0x3C, 0x00, // 73 + 0x10, 0x10, 0x3C, 0x10, 0x10, 0x12, 0x0C, 0x00, // 74 + 0x00, 0x00, 0x22, 0x22, 0x22, 0x26, 0x1A, 0x00, // 75 + 0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00, // 76 + 0x00, 0x00, 0x22, 0x22, 0x2A, 0x2A, 0x36, 0x00, // 77 + 0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, // 78 + 0x00, 0x00, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C, // 79 + 0x00, 0x00, 0x3E, 0x04, 0x08, 0x10, 0x3E, 0x00, // 7A + 0x0E, 0x18, 0x18, 0x30, 0x18, 0x18, 0x0E, 0x00, // 7B + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // 7C + 0x38, 0x0C, 0x0C, 0x06, 0x0C, 0x0C, 0x38, 0x00, // 7D + 0x2A, 0x15, 0x2A, 0x15, 0x2A, 0x15, 0x2A, 0x15, // 7E + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, // 7F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 80 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 81 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 82 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 83 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 84 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 85 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 86 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 87 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 88 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8A + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8C + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 91 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 92 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 93 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 94 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 95 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 96 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 97 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 98 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 99 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9A + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9B + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9C + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9D + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9E + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9F + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0 + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, 0xF7, 0xFF, // A1 + 0xEB, 0xEB, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A2 + 0xEB, 0xEB, 0xC1, 0xEB, 0xC1, 0xEB, 0xEB, 0xFF, // A3 + 0xF7, 0xE1, 0xD7, 0xE3, 0xF5, 0xC3, 0xF7, 0xFF, // A4 + 0xCF, 0xCD, 0xFB, 0xF7, 0xEF, 0xD9, 0xF9, 0xFF, // A5 + 0xEF, 0xD7, 0xD7, 0xEF, 0xD5, 0xDB, 0xE5, 0xFF, // A6 + 0xF7, 0xF7, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A7 + 0xF7, 0xEF, 0xDF, 0xDF, 0xDF, 0xEF, 0xF7, 0xFF, // A8 + 0xF7, 0xFB, 0xFD, 0xFD, 0xFD, 0xFB, 0xF7, 0xFF, // A9 + 0xF7, 0xD5, 0xE3, 0xF7, 0xE3, 0xD5, 0xF7, 0xFF, // AA + 0xFF, 0xF7, 0xF7, 0xC1, 0xF7, 0xF7, 0xFF, 0xFF, // AB + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xF7, 0xEF, // AC + 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, // AD + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, // AE + 0xFF, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xFF, 0xFF, // AF + 0xE3, 0xDD, 0xD9, 0xD5, 0xCD, 0xDD, 0xE3, 0xFF, // B0 + 0xF7, 0xE7, 0xF7, 0xF7, 0xF7, 0xF7, 0xE3, 0xFF, // B1 + 0xE3, 0xDD, 0xFD, 0xFB, 0xF7, 0xEF, 0xC1, 0xFF, // B2 + 0xC1, 0xFD, 0xFB, 0xF3, 0xFD, 0xDD, 0xE3, 0xFF, // B3 + 0xFB, 0xF3, 0xEB, 0xDB, 0xC1, 0xFB, 0xFB, 0xFF, // B4 + 0xC1, 0xDF, 0xC3, 0xFD, 0xFD, 0xDD, 0xE3, 0xFF, // B5 + 0xF3, 0xEF, 0xDF, 0xC3, 0xDD, 0xDD, 0xE3, 0xFF, // B6 + 0xC1, 0xFD, 0xFB, 0xF7, 0xEF, 0xEF, 0xEF, 0xFF, // B7 + 0xE3, 0xDD, 0xDD, 0xE3, 0xDD, 0xDD, 0xE3, 0xFF, // B8 + 0xE3, 0xDD, 0xDD, 0xE1, 0xFD, 0xFB, 0xE7, 0xFF, // B9 + 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, // BA + 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xF7, 0xF7, 0xEF, // BB + 0xFB, 0xF7, 0xEF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFF, // BC + 0xFF, 0xFF, 0xC1, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, // BD + 0xEF, 0xF7, 0xFB, 0xFD, 0xFB, 0xF7, 0xEF, 0xFF, // BE + 0xE3, 0xDD, 0xFB, 0xF7, 0xF7, 0xFF, 0xF7, 0xFF, // BF + 0xE3, 0xDD, 0xD5, 0xD1, 0xD3, 0xDF, 0xE1, 0xFF, // C0 + 0xF7, 0xEB, 0xDD, 0xDD, 0xC1, 0xDD, 0xDD, 0xFF, // C1 + 0xC3, 0xDD, 0xDD, 0xC3, 0xDD, 0xDD, 0xC3, 0xFF, // C2 + 0xE3, 0xDD, 0xDF, 0xDF, 0xDF, 0xDD, 0xE3, 0xFF, // C3 + 0xC3, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xC3, 0xFF, // C4 + 0xC1, 0xDF, 0xDF, 0xC3, 0xDF, 0xDF, 0xC1, 0xFF, // C5 + 0xC1, 0xDF, 0xDF, 0xC3, 0xDF, 0xDF, 0xDF, 0xFF, // C6 + 0xE1, 0xDF, 0xDF, 0xDF, 0xD9, 0xDD, 0xE1, 0xFF, // C7 + 0xDD, 0xDD, 0xDD, 0xC1, 0xDD, 0xDD, 0xDD, 0xFF, // C8 + 0xE3, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xE3, 0xFF, // C9 + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xDD, 0xE3, 0xFF, // CA + 0xDD, 0xDB, 0xD7, 0xCF, 0xD7, 0xDB, 0xDD, 0xFF, // CB + 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xC1, 0xFF, // CC + 0xDD, 0xC9, 0xD5, 0xD5, 0xDD, 0xDD, 0xDD, 0xFF, // CD + 0xDD, 0xDD, 0xCD, 0xD5, 0xD9, 0xDD, 0xDD, 0xFF, // CE + 0xE3, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE3, 0xFF, // CF + 0xC3, 0xDD, 0xDD, 0xC3, 0xDF, 0xDF, 0xDF, 0xFF, // D0 + 0xE3, 0xDD, 0xDD, 0xDD, 0xD5, 0xDB, 0xE5, 0xFF, // D1 + 0xC3, 0xDD, 0xDD, 0xC3, 0xD7, 0xDB, 0xDD, 0xFF, // D2 + 0xE3, 0xDD, 0xDF, 0xE3, 0xFD, 0xDD, 0xE3, 0xFF, // D3 + 0xC1, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, // D4 + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE3, 0xFF, // D5 + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xEB, 0xF7, 0xFF, // D6 + 0xDD, 0xDD, 0xDD, 0xD5, 0xD5, 0xC9, 0xDD, 0xFF, // D7 + 0xDD, 0xDD, 0xEB, 0xF7, 0xEB, 0xDD, 0xDD, 0xFF, // D8 + 0xDD, 0xDD, 0xEB, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, // D9 + 0xC1, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xC1, 0xFF, // DA + 0xE1, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xE1, 0xFF, // DB + 0xFF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFF, 0xFF, // DC + 0xC3, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xC3, 0xFF, // DD + 0xF7, 0xEB, 0xD5, 0xF7, 0xF7, 0xF7, 0xF7, 0xFF, // DE + 0xF1, 0xEE, 0xC3, 0xEF, 0xC3, 0xEE, 0xF1, 0xFF, // DF + 0xF3, 0xED, 0xD2, 0xD6, 0xD6, 0xD2, 0xED, 0xF3, // E0 + 0xFF, 0xFF, 0xE3, 0xFD, 0xE1, 0xDD, 0xE1, 0xFF, // E1 + 0xDF, 0xDF, 0xC3, 0xDD, 0xDD, 0xDD, 0xC3, 0xFF, // E2 + 0xFF, 0xFF, 0xE1, 0xDF, 0xDF, 0xDF, 0xE1, 0xFF, // E3 + 0xFD, 0xFD, 0xE1, 0xDD, 0xDD, 0xDD, 0xE1, 0xFF, // E4 + 0xFF, 0xFF, 0xE3, 0xDD, 0xC1, 0xDF, 0xE1, 0xFF, // E5 + 0xF3, 0xED, 0xEF, 0xC3, 0xEF, 0xEF, 0xEF, 0xFF, // E6 + 0xFF, 0xFF, 0xE3, 0xDD, 0xDD, 0xE1, 0xFD, 0xE3, // E7 + 0xDF, 0xDF, 0xC3, 0xDD, 0xDD, 0xDD, 0xDD, 0xFF, // E8 + 0xF7, 0xFF, 0xE7, 0xF7, 0xF7, 0xF7, 0xE3, 0xFF, // E9 + 0xFB, 0xFF, 0xF3, 0xFB, 0xFB, 0xFB, 0xDB, 0xE7, // EA + 0xDF, 0xDF, 0xDD, 0xDB, 0xC7, 0xDB, 0xDD, 0xFF, // EB + 0xE7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xE3, 0xFF, // EC + 0xFF, 0xFF, 0xC9, 0xD5, 0xD5, 0xD5, 0xDD, 0xFF, // ED + 0xFF, 0xFF, 0xC3, 0xDD, 0xDD, 0xDD, 0xDD, 0xFF, // EE + 0xFF, 0xFF, 0xE3, 0xDD, 0xDD, 0xDD, 0xE3, 0xFF, // EF + 0xFF, 0xFF, 0xC3, 0xDD, 0xDD, 0xC3, 0xDF, 0xDF, // F0 + 0xFF, 0xFF, 0xE1, 0xDD, 0xDD, 0xE1, 0xFD, 0xFD, // F1 + 0xFF, 0xFF, 0xD1, 0xCF, 0xDF, 0xDF, 0xDF, 0xFF, // F2 + 0xFF, 0xFF, 0xE1, 0xDF, 0xE3, 0xFD, 0xC3, 0xFF, // F3 + 0xEF, 0xEF, 0xC3, 0xEF, 0xEF, 0xED, 0xF3, 0xFF, // F4 + 0xFF, 0xFF, 0xDD, 0xDD, 0xDD, 0xD9, 0xE5, 0xFF, // F5 + 0xFF, 0xFF, 0xDD, 0xDD, 0xDD, 0xEB, 0xF7, 0xFF, // F6 + 0xFF, 0xFF, 0xDD, 0xDD, 0xD5, 0xD5, 0xC9, 0xFF, // F7 + 0xFF, 0xFF, 0xDD, 0xEB, 0xF7, 0xEB, 0xDD, 0xFF, // F8 + 0xFF, 0xFF, 0xDD, 0xDD, 0xDD, 0xE1, 0xFD, 0xE3, // F9 + 0xFF, 0xFF, 0xC1, 0xFB, 0xF7, 0xEF, 0xC1, 0xFF, // FA + 0xF1, 0xE7, 0xE7, 0xCF, 0xE7, 0xE7, 0xF1, 0xFF, // FB + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, // FC + 0xC7, 0xF3, 0xF3, 0xF9, 0xF3, 0xF3, 0xC7, 0xFF, // FD + 0xD5, 0xEA, 0xD5, 0xEA, 0xD5, 0xEA, 0xD5, 0xEA, // FE + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // FF +}; + +/* + Embedded source code compiled with: + + sokol-shdc -i debugtext.glsl -o debugtext.h -l glsl410:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + in vec2 position; + in vec2 texcoord0; + in vec4 color0; + out vec2 uv; + out vec4 color; + void main() { + gl_Position = vec4(position * vec2(2.0, -2.0) + vec2(-1.0, +1.0), 0.0, 1.0); + uv = texcoord0; + color = color0; + } + @end + + @fs fs + layout(binding=0) uniform texture2D tex; + layout(binding=0) uniform sampler smp; + in vec2 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = texture(sampler2D(tex, smp), uv).xxxx * color; + } + @end + + @program debugtext vs fs +*/ +#if defined(SOKOL_GLCORE) +/* + #version 410 + + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 uv; + layout(location = 1) in vec2 texcoord0; + layout(location = 1) out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = vec4(fma(position, vec2(2.0, -2.0), vec2(-1.0, 1.0)), 0.0, 1.0); + uv = texcoord0; + color = color0; + } +*/ +static const uint8_t _sdtx_vs_source_glsl410[343] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, + 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76, + 0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x31,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x66, + 0x6d,0x61,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x76,0x65,0x63, + 0x32,0x28,0x32,0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c,0x20,0x76,0x65, + 0x63,0x32,0x28,0x2d,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x29,0x2c,0x20, + 0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75, + 0x76,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 410 + + uniform sampler2D tex_smp; + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec2 uv; + layout(location = 1) in vec4 color; + + void main() + { + frag_color = texture(tex_smp, uv).xxxx * color; + } +*/ +static const uint8_t _sdtx_fs_source_glsl410[224] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f, + 0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x29,0x2e,0x78,0x78, + 0x78,0x78,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + +}; +#elif defined(SOKOL_GLES3) +/* + #version 300 es + + layout(location = 0) in vec2 position; + out vec2 uv; + layout(location = 1) in vec2 texcoord0; + out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = vec4(position * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); + uv = texcoord0; + color = color0; + } +*/ +static const uint8_t _sdtx_vs_source_glsl300es[301] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74, + 0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x2a,0x20,0x76,0x65,0x63,0x32,0x28,0x32,0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e, + 0x30,0x29,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x2d,0x31,0x2e,0x30,0x2c,0x20, + 0x31,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f, + 0x72,0x64,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D tex_smp; + + layout(location = 0) out highp vec4 frag_color; + in highp vec2 uv; + in highp vec4 color; + + void main() + { + frag_color = texture(tex_smp, uv).xxxx * color; + } +*/ +static const uint8_t _sdtx_fs_source_glsl300es[255] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x75, + 0x76,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x29,0x2e,0x78,0x78,0x78, + 0x78,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float2 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(fma(in.position, float2(2.0, -2.0), float2(-1.0, 1.0)), 0.0, 1.0); + out.uv = in.texcoord0; + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sdtx_vs_bytecode_metal_macos[2892] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x4c,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x54,0x27,0xd2,0x9c,0x99,0x28,0x3d, + 0x28,0xcf,0x09,0x1c,0x55,0xac,0x1d,0xfa,0x23,0x34,0x9b,0xc3,0x82,0x58,0xe9,0xeb, + 0xdc,0x0c,0xa5,0xd3,0x0c,0xa1,0xbf,0xa0,0xe7,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x37,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03,0x00,0x04,0x04,0x06, + 0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b, + 0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x20,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x85,0x02,0x00,0x00,0x0b,0x82,0x20,0x00, + 0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49, + 0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62, + 0x80,0x10,0x45,0x02,0x42,0x92,0x0b,0x42,0x84,0x10,0x32,0x14,0x38,0x08,0x18,0x49, + 0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72, + 0x24,0x07,0xc8,0x08,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00, + 0x51,0x18,0x00,0x00,0x6c,0x00,0x00,0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x00,0x8a,0x08,0x07,0x78,0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73, + 0xa8,0x07,0x77,0x18,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8, + 0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1,0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41, + 0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c,0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00, + 0x06,0x77,0x78,0x87,0x36,0x10,0x87,0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87, + 0x79,0x00,0x08,0x77,0x78,0x87,0x36,0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36, + 0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48, + 0x87,0x76,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc, + 0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1, + 0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d, + 0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08, + 0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36, + 0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d, + 0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc,0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda, + 0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77, + 0x68,0x83,0x79,0x48,0x87,0x73,0x70,0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90, + 0x87,0x77,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, + 0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21, + 0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d,0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d, + 0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87, + 0x36,0x80,0x07,0x79,0x78,0x07,0x7a,0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36, + 0x10,0x87,0x7a,0x30,0x07,0x73,0x28,0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28, + 0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc,0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc, + 0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72, + 0x00,0x36,0x18,0xc2,0xff,0xff,0xff,0xff,0x0f,0x80,0x04,0x50,0x1b,0x8c,0xe1,0xff, + 0xff,0xff,0xff,0x07,0x40,0x02,0x28,0x00,0x49,0x18,0x00,0x00,0x03,0x00,0x00,0x00, + 0x13,0x82,0x60,0x42,0x20,0x4c,0x08,0x06,0x00,0x00,0x00,0x00,0x89,0x20,0x00,0x00, + 0x11,0x00,0x00,0x00,0x32,0x22,0x08,0x09,0x20,0x64,0x85,0x04,0x13,0x22,0xa4,0x84, + 0x04,0x13,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x88,0x8c,0x0b,0x84,0x84,0x4c, + 0x10,0x34,0x33,0x00,0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x91,0x42,0x4c, + 0x84,0x10,0x15,0x22,0x22,0x82,0x6c,0x20,0x60,0x8e,0x00,0x0c,0x52,0x20,0x87,0x11, + 0x88,0x64,0x04,0x00,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03, + 0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71, + 0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8, + 0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06, + 0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30, + 0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72, + 0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07, + 0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20, + 0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d, + 0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80, + 0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07, + 0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75, + 0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0, + 0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07, + 0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d, + 0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0, + 0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07, + 0x7a,0x30,0x07,0x72,0x30,0x84,0x29,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x18, + 0xc2,0x1c,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64,0x81,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47, + 0xc6,0x04,0x43,0xca,0x12,0x18,0x01,0x28,0x82,0x42,0x28,0x08,0xd2,0xb1,0x84,0x07, + 0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xb1,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10, + 0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0x46,0x42,0x20, + 0x80,0x72,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x24, + 0x02,0x22,0x24,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c, + 0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46, + 0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0x80,0x10,0x43,0x8c,0x44,0x48,0x8c,0x64, + 0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x41,0x8e,0x44,0x48,0x84,0x64,0xe0,0x16, + 0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26, + 0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x40,0x12,0x72,0x61, + 0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f, + 0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x64,0x61,0x19, + 0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5, + 0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5, + 0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x90,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99, + 0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39, + 0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f, + 0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64, + 0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3, + 0x1b,0xc2,0x20,0x0f,0x02,0x21,0x11,0x22,0x21,0x13,0x42,0x71,0xa9,0x9b,0x2b,0x93, + 0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0xa1,0x61,0xc6,0xf6,0x16,0x46,0x47,0xc3,0x62, + 0xec,0x8d,0xed,0x4d,0x6e,0x08,0x83,0x3c,0x88,0x85,0x44,0xc8,0x85,0x4c,0x08,0x46, + 0x26,0x2c,0x4d,0xce,0x05,0xee,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x8d,0xcb,0x19,0xdb, + 0x17,0xd4,0xdb,0x5c,0x1a,0x5d,0xda,0x9b,0xdb,0x10,0x05,0xd1,0x90,0x08,0xb9,0x90, + 0x09,0xd9,0x86,0x18,0x48,0x85,0x64,0x08,0x47,0x28,0x2c,0x4d,0xce,0xc5,0xae,0x4c, + 0x8e,0xae,0x0c,0xef,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x52,0x58,0x9a,0x9c,0x0b,0xdb, + 0xdb,0x58,0x18,0x5d,0xda,0x9b,0xdb,0x57,0x9a,0x1b,0x59,0x19,0x1e,0xbd,0xb3,0x32, + 0xb7,0x32,0xb9,0x30,0xba,0x32,0x32,0x94,0xaf,0xaf,0xb0,0x34,0xb9,0x2f,0x38,0xb6, + 0xb0,0xb1,0x32,0xb4,0x37,0x36,0xb2,0x32,0xb9,0xaf,0xaf,0x14,0x22,0x70,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x43,0xa8,0x64,0x40,0x3c,0xe4,0x4b,0x86,0x44,0x40,0xc0, + 0x00,0x89,0x10,0x09,0x99,0x90,0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6,0x26,0x47, + 0x06,0x33,0x84,0x4a,0x04,0xc4,0x43,0xbe,0x44,0x48,0x04,0x04,0x0c,0x90,0x08,0x91, + 0x90,0x09,0x19,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x84,0x40,0x3c, + 0xe4,0x4b,0x88,0x44,0x40,0xc0,0x00,0x89,0x90,0x0b,0x99,0x90,0x32,0x18,0x62,0x20, + 0x62,0x80,0x90,0x01,0x62,0x06,0x43,0x8c,0x02,0x40,0x3a,0xe4,0x0c,0x46,0x44,0xec, + 0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e,0xef,0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0, + 0x06,0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3,0x30,0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0, + 0x83,0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39,0x94,0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18, + 0xb1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8,0x43,0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b, + 0xb8,0xc3,0x94,0xc0,0x18,0x41,0x85,0x43,0x3a,0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8, + 0xc3,0x39,0xd4,0x43,0x38,0x9c,0x43,0x39,0xfc,0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43, + 0x3a,0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11,0x53,0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38, + 0xbc,0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43,0x39,0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4, + 0xc3,0x3b,0xb8,0xc3,0x3c,0x4c,0x19,0x14,0xc6,0x19,0xa1,0x84,0x43,0x3a,0xc8,0x83, + 0x1b,0xd8,0x43,0x39,0xc8,0x03,0x3d,0x94,0x03,0x3e,0x4c,0x09,0xd0,0x00,0x00,0x00, + 0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66, + 0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73, + 0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e, + 0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b, + 0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b, + 0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20, + 0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0, + 0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61, + 0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83, + 0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87, + 0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76, + 0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98, + 0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30, + 0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61, + 0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43, + 0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b, + 0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7, + 0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18, + 0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90, + 0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1, + 0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d, + 0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c, + 0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d, + 0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54, + 0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4, + 0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18, + 0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0, + 0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1, + 0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3, + 0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c, + 0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19, + 0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5, + 0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0, + 0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81, + 0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d, + 0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39, + 0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77, + 0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef, + 0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07, + 0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73, + 0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00, + 0x05,0x00,0x00,0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x16,0xd0,0x00,0x48,0xe4,0x17, + 0x0c,0xe0,0x57,0x76,0x71,0xdb,0x00,0x00,0x61,0x20,0x00,0x00,0x1b,0x00,0x00,0x00, + 0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0xb4,0x63,0x11,0x40, + 0x60,0x1c,0x73,0x10,0x83,0xd0,0x34,0x94,0x33,0x00,0x14,0x63,0x09,0x20,0x08,0x82, + 0x20,0x18,0x80,0x20,0x08,0x82,0xe0,0x30,0x96,0x00,0x82,0x20,0x88,0xff,0x02,0x08, + 0x82,0x20,0xfe,0xcd,0x00,0x90,0xcc,0x41,0x54,0x15,0x35,0xd1,0xcc,0x00,0x10,0x8c, + 0x11,0x80,0x20,0x08,0xe2,0xdf,0x08,0xc0,0x0c,0x00,0x00,0x00,0x23,0x06,0xc6,0x10, + 0x54,0x0e,0x72,0x0c,0x32,0x04,0xc7,0x32,0xc8,0x10,0x1c,0xcd,0x6c,0xc3,0x01,0x01, + 0xb3,0x0d,0x01,0x14,0xcc,0x36,0x04,0x83,0x90,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float2 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv).xxxx * in.color; + return out; + } +*/ +static const uint8_t _sdtx_fs_bytecode_metal_macos[3033] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xd9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x60,0xb0,0xd5,0x97,0xc2,0xac,0x24, + 0x52,0x1f,0x4a,0x56,0x12,0x1a,0x52,0xd7,0xb5,0xc0,0x14,0xee,0xce,0x96,0x55,0xcf, + 0x7d,0x2c,0x16,0x4c,0x83,0x17,0xaf,0xc3,0x9d,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xec,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xb8,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x30,0x8c,0x20,0x00,0x47,0x49, + 0x53,0x44,0x09,0x93,0xff,0x4f,0xc4,0x35,0x51,0x11,0xf1,0xdb,0xc3,0x3f,0x8d,0x11, + 0x00,0x83,0x08,0x44,0x70,0x91,0x34,0x45,0x94,0x30,0xf9,0xbf,0x04,0x30,0xcf,0x42, + 0x44,0xff,0x34,0x46,0x00,0x0c,0x22,0x18,0x42,0x29,0xc4,0x08,0xe5,0x10,0x9a,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0xca,0x19,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xb2,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83, + 0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78, + 0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90, + 0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76, + 0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20, + 0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30, + 0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07, + 0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71, + 0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20, + 0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f, + 0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76, + 0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50, + 0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78, + 0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x49,0x00,0x00,0x08,0x00, + 0x00,0x00,0x00,0x00,0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64, + 0x81,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, + 0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50, + 0x10,0x65,0x40,0x70,0x2c,0xe1,0x01,0x00,0x00,0x79,0x18,0x00,0x00,0xd2,0x00,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43, + 0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45, + 0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x84,0x67,0x61,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x51,0x58, + 0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d, + 0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0, + 0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4, + 0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd, + 0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45, + 0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d, + 0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95, + 0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69,0x72,0x2e,0x70, + 0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8, + 0x64,0x28,0xd4,0xd9,0x0d,0x91,0x96,0xe1,0xb1,0x9e,0xeb,0xc1,0x9e,0xec,0x81,0x1e, + 0xed,0x91,0x9e,0x8d,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x5b,0x4c,0x0a, + 0x8b,0xb1,0x37,0xb6,0x37,0xb9,0x21,0xd2,0x22,0x3c,0xd6,0xd3,0x3d,0xd8,0x93,0x3d, + 0xd0,0x13,0x3d,0xd2,0xe3,0x71,0x09,0x4b,0x93,0x73,0xa1,0x2b,0xc3,0xa3,0xab,0x93, + 0x2b,0xa3,0x14,0x96,0x26,0xe7,0xc2,0xf6,0x36,0x16,0x46,0x97,0xf6,0xe6,0xf6,0x95, + 0xe6,0x46,0x56,0x86,0x47,0x25,0x2c,0x4d,0xce,0x65,0x2e,0xac,0x0d,0x8e,0xad,0x8c, + 0x18,0x5d,0x19,0x1e,0x5d,0x9d,0x5c,0x99,0x0c,0x19,0x8f,0x19,0xdb,0x5b,0x18,0x1d, + 0x0b,0xc8,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x0f,0x07,0xba,0x32,0xbc,0x21,0xd4,0x42, + 0x3c,0x60,0xf0,0x84,0xc1,0x32,0x2c,0xc2,0x23,0x06,0x0f,0xf4,0x8c,0xc1,0x23,0x3d, + 0x64,0xc0,0x25,0x2c,0x4d,0xce,0x65,0x2e,0xac,0x0d,0x8e,0xad,0x4c,0x8e,0xc7,0x5c, + 0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x87,0xb9,0x36,0xb8,0x21,0xd2,0x72,0x3c,0x66,0xf0, + 0x84,0xc1,0x32,0x2c,0xc2,0x03,0x3d,0x67,0xf0,0x48,0x0f,0x1a,0x0c,0x41,0x1e,0xee, + 0xf9,0x9e,0x32,0x78,0xd2,0x60,0x88,0x91,0x00,0x4f,0xf5,0xa8,0x01,0xaf,0xb0,0x34, + 0xb9,0x96,0x30,0xb6,0xb4,0xb0,0xb9,0x96,0xb9,0xb1,0x37,0xb8,0xb2,0x39,0x94,0xb6, + 0xb0,0x34,0x37,0x98,0x94,0x21,0xc4,0xd3,0x06,0x0f,0x1b,0x10,0x0b,0x4b,0x93,0x6b, + 0x09,0x63,0x4b,0x0b,0x9b,0x6b,0x99,0x1b,0x7b,0x83,0x2b,0x6b,0xa1,0x2b,0xc3,0xa3, + 0xab,0x93,0x2b,0x9b,0x1b,0x62,0x3c,0x6f,0xf0,0xb4,0xc1,0xe3,0x06,0xc4,0xc2,0xd2, + 0xe4,0x5a,0xc2,0xd8,0xd2,0xc2,0xe6,0x5a,0xe6,0xc6,0xde,0xe0,0xca,0x5a,0xe6,0xc2, + 0xda,0xe0,0xd8,0xca,0xe4,0xe6,0x86,0x18,0x4f,0x1c,0x3c,0x6d,0xf0,0xc0,0xc1,0x10, + 0xe2,0x79,0x83,0x27,0x0e,0x46,0x44,0xec,0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e, + 0xef,0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0,0x06,0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3, + 0x30,0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39, + 0x94,0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18,0xb1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8, + 0x43,0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b,0xb8,0xc3,0x94,0xc0,0x18,0x41,0x85,0x43, + 0x3a,0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8,0xc3,0x39,0xd4,0x43,0x38,0x9c,0x43,0x39, + 0xfc,0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43,0x3a,0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11, + 0x53,0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38,0xbc,0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43, + 0x39,0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x3c,0x4c,0x19,0x14, + 0xc6,0x19,0xc1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0x98,0x83,0x3c,0x84,0xc3,0x39,0xb4, + 0x43,0x39,0xb8,0x03,0x3d,0x4c,0x09,0xd6,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01, + 0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e, + 0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00, + 0x00,0x14,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00, + 0x00,0x14,0x47,0x00,0x88,0x8d,0x00,0x90,0x1a,0x01,0xa8,0x01,0x12,0x33,0x00,0x14, + 0x66,0x00,0x08,0x8c,0x00,0x00,0x00,0x00,0x00,0x23,0x06,0xca,0x10,0x4c,0x09,0xb2, + 0x10,0x46,0x11,0x0c,0x32,0x04,0x03,0x62,0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32, + 0x08,0x88,0x01,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x06,0xe0,0x90,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float2 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(fma(in.position, float2(2.0, -2.0), float2(-1.0, 1.0)), 0.0, 1.0); + out.uv = in.texcoord0; + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sdtx_vs_bytecode_metal_ios[2876] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3c,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x30,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x54,0x27,0x6d,0x17,0x18,0x1b,0xd1, + 0x64,0x91,0xfe,0x8a,0x4c,0x49,0xdf,0xe3,0x15,0x74,0xcb,0x3e,0x43,0xde,0xeb,0xfa, + 0x8e,0xf0,0xf5,0xf8,0x4b,0xd2,0xac,0x23,0xe4,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x37,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03,0x00,0x04,0x04,0x06, + 0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b, + 0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x18,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x83,0x02,0x00,0x00,0x0b,0x82,0x20,0x00, + 0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49, + 0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62, + 0x80,0x10,0x45,0x02,0x42,0x92,0x0b,0x42,0x84,0x10,0x32,0x14,0x38,0x08,0x18,0x49, + 0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72, + 0x24,0x07,0xc8,0x08,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00, + 0x51,0x18,0x00,0x00,0x6c,0x00,0x00,0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x00,0x8a,0x08,0x07,0x78,0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73, + 0xa8,0x07,0x77,0x18,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8, + 0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1,0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41, + 0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c,0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00, + 0x06,0x77,0x78,0x87,0x36,0x10,0x87,0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87, + 0x79,0x00,0x08,0x77,0x78,0x87,0x36,0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36, + 0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48, + 0x87,0x76,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc, + 0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1, + 0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d, + 0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08, + 0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36, + 0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d, + 0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc,0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda, + 0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77, + 0x68,0x83,0x79,0x48,0x87,0x73,0x70,0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90, + 0x87,0x77,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07, + 0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21, + 0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d,0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d, + 0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87, + 0x36,0x80,0x07,0x79,0x78,0x07,0x7a,0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36, + 0x10,0x87,0x7a,0x30,0x07,0x73,0x28,0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28, + 0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc,0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc, + 0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72, + 0x00,0x36,0x18,0xc2,0xff,0xff,0xff,0xff,0x0f,0x80,0x04,0x50,0x1b,0x8c,0xe1,0xff, + 0xff,0xff,0xff,0x07,0x40,0x02,0x28,0x00,0x49,0x18,0x00,0x00,0x03,0x00,0x00,0x00, + 0x13,0x82,0x60,0x42,0x20,0x4c,0x08,0x06,0x00,0x00,0x00,0x00,0x89,0x20,0x00,0x00, + 0x11,0x00,0x00,0x00,0x32,0x22,0x08,0x09,0x20,0x64,0x85,0x04,0x13,0x22,0xa4,0x84, + 0x04,0x13,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x88,0x8c,0x0b,0x84,0x84,0x4c, + 0x10,0x34,0x33,0x00,0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x91,0x42,0x4c, + 0x84,0x10,0x15,0x22,0x22,0x82,0x6c,0x20,0x60,0x8e,0x00,0x0c,0x52,0x20,0x87,0x11, + 0x88,0x64,0x04,0x00,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03, + 0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79, + 0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a, + 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10, + 0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07, + 0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0, + 0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06, + 0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6, + 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10, + 0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07, + 0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72, + 0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0, + 0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07, + 0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71, + 0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80, + 0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f, + 0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76, + 0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20, + 0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07, + 0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74, + 0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0, + 0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x02, + 0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0xcc,0x01,0x04,0x80,0x00,0x00,0x00, + 0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10, + 0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0xca,0x12,0x18,0x01,0x28, + 0x82,0x42,0x28,0x08,0xd2,0xb1,0x04,0x09,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00, + 0xb1,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9, + 0xb9,0xb4,0x37,0xb7,0x21,0x46,0x42,0x20,0x80,0x72,0x50,0xb9,0x1b,0x43,0x0b,0x93, + 0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x24,0x02,0x22,0x24,0x05,0xe3,0x20,0x08,0x0e, + 0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd, + 0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e, + 0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88, + 0x80,0x10,0x43,0x8c,0x44,0x48,0x8c,0x64,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04, + 0x41,0x8e,0x44,0x48,0x84,0x64,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97, + 0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26, + 0xc6,0x56,0x36,0x44,0x40,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69, + 0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61, + 0x62,0x6c,0x65,0x43,0x04,0x64,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1, + 0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d, + 0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x90,0x86, + 0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c, + 0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61, + 0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65, + 0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d, + 0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x20,0x0f,0x02,0x21,0x11,0x22,0x21, + 0x13,0x42,0x71,0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0xa1,0x61, + 0xc6,0xf6,0x16,0x46,0x47,0xc3,0x62,0xec,0x8d,0xed,0x4d,0x6e,0x08,0x83,0x3c,0x88, + 0x85,0x44,0xc8,0x85,0x4c,0x08,0x46,0x26,0x2c,0x4d,0xce,0x05,0xee,0x6d,0x2e,0x8d, + 0x2e,0xed,0xcd,0x8d,0xcb,0x19,0xdb,0x17,0xd4,0xdb,0x5c,0x1a,0x5d,0xda,0x9b,0xdb, + 0x10,0x05,0xd1,0x90,0x08,0xb9,0x90,0x09,0xd9,0x86,0x18,0x48,0x85,0x64,0x08,0x47, + 0x28,0x2c,0x4d,0xce,0xc5,0xae,0x4c,0x8e,0xae,0x0c,0xef,0x2b,0xcd,0x0d,0xae,0x8e, + 0x8e,0x52,0x58,0x9a,0x9c,0x0b,0xdb,0xdb,0x58,0x18,0x5d,0xda,0x9b,0xdb,0x57,0x9a, + 0x1b,0x59,0x19,0x1e,0xbd,0xb3,0x32,0xb7,0x32,0xb9,0x30,0xba,0x32,0x32,0x94,0xaf, + 0xaf,0xb0,0x34,0xb9,0x2f,0x38,0xb6,0xb0,0xb1,0x32,0xb4,0x37,0x36,0xb2,0x32,0xb9, + 0xaf,0xaf,0x14,0x22,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x43,0xa8,0x64,0x40, + 0x3c,0xe4,0x4b,0x86,0x44,0x40,0xc0,0x00,0x89,0x10,0x09,0x99,0x90,0x30,0x60,0x42, + 0x57,0x86,0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84,0x4a,0x04,0xc4,0x43,0xbe,0x44, + 0x48,0x04,0x04,0x0c,0x90,0x08,0x91,0x90,0x09,0x19,0x03,0x1a,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x43,0xa8,0x84,0x40,0x3c,0xe4,0x4b,0x88,0x44,0x40,0xc0,0x00,0x89,0x90, + 0x0b,0x99,0x90,0x32,0x18,0x62,0x20,0x62,0x80,0x90,0x01,0x62,0x06,0x43,0x8c,0x02, + 0x40,0x3a,0xe4,0x0c,0x46,0x44,0xec,0xc0,0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e,0xef, + 0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0,0x06,0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3,0x30, + 0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39,0x94, + 0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18,0xb1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8,0x43, + 0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b,0xb8,0xc3,0x94,0xc0,0x18,0x41,0x85,0x43,0x3a, + 0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8,0xc3,0x39,0xd4,0x43,0x38,0x9c,0x43,0x39,0xfc, + 0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43,0x3a,0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11,0x53, + 0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38,0xbc,0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43,0x39, + 0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x3c,0x4c,0x19,0x14,0xc6, + 0x19,0xa1,0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8,0x43,0x39,0xc8,0x03,0x3d,0x94,0x03, + 0x3e,0x4c,0x09,0xd0,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00, + 0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3, + 0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10, + 0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30, + 0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03, + 0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07, + 0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e, + 0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d, + 0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b, + 0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76, + 0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90, + 0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87, + 0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e, + 0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c, + 0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca, + 0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8, + 0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82, + 0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83, + 0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f, + 0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec, + 0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc, + 0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0, + 0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60, + 0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e, + 0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1, + 0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43, + 0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c, + 0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72, + 0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1, + 0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b, + 0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8, + 0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60, + 0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0, + 0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e, + 0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8, + 0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6, + 0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc, + 0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78, + 0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10, + 0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e, + 0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0, + 0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07, + 0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x50,0x30,0x00, + 0xd2,0xd0,0x16,0xd0,0x00,0x48,0xe4,0x17,0x0c,0xe0,0x57,0x76,0x71,0xdb,0x00,0x00, + 0x61,0x20,0x00,0x00,0x1b,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00, + 0x10,0x00,0x00,0x00,0xb4,0x63,0x11,0x40,0x60,0x1c,0x73,0x10,0x83,0xd0,0x34,0x94, + 0x33,0x00,0x14,0x63,0x09,0x20,0x08,0x82,0x20,0x18,0x80,0x20,0x08,0x82,0xe0,0x30, + 0x96,0x00,0x82,0x20,0x88,0xff,0x02,0x08,0x82,0x20,0xfe,0xcd,0x00,0x90,0xcc,0x41, + 0x54,0x15,0x35,0xd1,0xcc,0x00,0x10,0x8c,0x11,0x80,0x20,0x08,0xe2,0xdf,0x08,0xc0, + 0x0c,0x00,0x00,0x00,0x23,0x06,0xc6,0x10,0x54,0x0e,0x72,0x0c,0x32,0x04,0xc7,0x32, + 0xc8,0x10,0x1c,0xcd,0x6c,0xc3,0x01,0x01,0xb3,0x0d,0x01,0x14,0xcc,0x36,0x04,0x83, + 0x90,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float2 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv).xxxx * in.color; + return out; + } + +*/ +static const uint8_t _sdtx_fs_bytecode_metal_ios[3033] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xd9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x64,0xff,0xce,0xd3,0x48,0x71,0xaf, + 0x68,0xf3,0x79,0x4e,0x9f,0x9e,0xbf,0x16,0xda,0xcc,0x0f,0x60,0xbc,0x56,0x43,0x8f, + 0x4b,0x45,0xa7,0x93,0x35,0x40,0xfe,0x60,0x45,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xe4,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xb6,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x30,0x8c,0x20,0x00,0x47,0x49, + 0x53,0x44,0x09,0x93,0xff,0x4f,0xc4,0x35,0x51,0x11,0xf1,0xdb,0xc3,0x3f,0x8d,0x11, + 0x00,0x83,0x08,0x44,0x70,0x91,0x34,0x45,0x94,0x30,0xf9,0xbf,0x04,0x30,0xcf,0x42, + 0x44,0xff,0x34,0x46,0x00,0x0c,0x22,0x18,0x42,0x29,0xc4,0x08,0xe5,0x10,0x9a,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0xca,0x19,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xa8,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51, + 0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78, + 0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60, + 0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07, + 0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, + 0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30, + 0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0, + 0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, + 0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, + 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90, + 0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06, + 0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, + 0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0, + 0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07, + 0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72, + 0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10, + 0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73, + 0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03, + 0x04,0x80,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, + 0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0x41,0x02,0x00, + 0x00,0x79,0x18,0x00,0x00,0xd2,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x21,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1, + 0x95,0x0d,0x11,0x9e,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc, + 0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9, + 0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89, + 0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac, + 0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a, + 0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32, + 0x39,0x3e,0x61,0x69,0x72,0x2e,0x70,0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76, + 0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0x28,0xd4,0xd9,0x0d,0x91,0x96,0xe1,0xb1, + 0x9e,0xeb,0xc1,0x9e,0xec,0x81,0x1e,0xed,0x91,0x9e,0x8d,0x4b,0xdd,0x5c,0x99,0x1c, + 0x0a,0xdb,0xdb,0x98,0x5b,0x4c,0x0a,0x8b,0xb1,0x37,0xb6,0x37,0xb9,0x21,0xd2,0x22, + 0x3c,0xd6,0xd3,0x3d,0xd8,0x93,0x3d,0xd0,0x13,0x3d,0xd2,0xe3,0x71,0x09,0x4b,0x93, + 0x73,0xa1,0x2b,0xc3,0xa3,0xab,0x93,0x2b,0xa3,0x14,0x96,0x26,0xe7,0xc2,0xf6,0x36, + 0x16,0x46,0x97,0xf6,0xe6,0xf6,0x95,0xe6,0x46,0x56,0x86,0x47,0x25,0x2c,0x4d,0xce, + 0x65,0x2e,0xac,0x0d,0x8e,0xad,0x8c,0x18,0x5d,0x19,0x1e,0x5d,0x9d,0x5c,0x99,0x0c, + 0x19,0x8f,0x19,0xdb,0x5b,0x18,0x1d,0x0b,0xc8,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x0f, + 0x07,0xba,0x32,0xbc,0x21,0xd4,0x42,0x3c,0x60,0xf0,0x84,0xc1,0x32,0x2c,0xc2,0x23, + 0x06,0x0f,0xf4,0x8c,0xc1,0x23,0x3d,0x64,0xc0,0x25,0x2c,0x4d,0xce,0x65,0x2e,0xac, + 0x0d,0x8e,0xad,0x4c,0x8e,0xc7,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x87,0xb9,0x36, + 0xb8,0x21,0xd2,0x72,0x3c,0x66,0xf0,0x84,0xc1,0x32,0x2c,0xc2,0x03,0x3d,0x67,0xf0, + 0x48,0x0f,0x1a,0x0c,0x41,0x1e,0xee,0xf9,0x9e,0x32,0x78,0xd2,0x60,0x88,0x91,0x00, + 0x4f,0xf5,0xa8,0x01,0xaf,0xb0,0x34,0xb9,0x96,0x30,0xb6,0xb4,0xb0,0xb9,0x96,0xb9, + 0xb1,0x37,0xb8,0xb2,0x39,0x94,0xb6,0xb0,0x34,0x37,0x98,0x94,0x21,0xc4,0xd3,0x06, + 0x0f,0x1b,0x10,0x0b,0x4b,0x93,0x6b,0x09,0x63,0x4b,0x0b,0x9b,0x6b,0x99,0x1b,0x7b, + 0x83,0x2b,0x6b,0xa1,0x2b,0xc3,0xa3,0xab,0x93,0x2b,0x9b,0x1b,0x62,0x3c,0x6f,0xf0, + 0xb4,0xc1,0xe3,0x06,0xc4,0xc2,0xd2,0xe4,0x5a,0xc2,0xd8,0xd2,0xc2,0xe6,0x5a,0xe6, + 0xc6,0xde,0xe0,0xca,0x5a,0xe6,0xc2,0xda,0xe0,0xd8,0xca,0xe4,0xe6,0x86,0x18,0x4f, + 0x1c,0x3c,0x6d,0xf0,0xc0,0xc1,0x10,0xe2,0x79,0x83,0x27,0x0e,0x46,0x44,0xec,0xc0, + 0x0e,0xf6,0xd0,0x0e,0x6e,0xd0,0x0e,0xef,0x40,0x0e,0xf5,0xc0,0x0e,0xe5,0xe0,0x06, + 0xe6,0xc0,0x0e,0xe1,0x70,0x0e,0xf3,0x30,0x45,0x08,0x86,0x11,0x0a,0x3b,0xb0,0x83, + 0x3d,0xb4,0x83,0x1b,0xa4,0x03,0x39,0x94,0x83,0x3b,0xd0,0xc3,0x94,0xa0,0x18,0xb1, + 0x84,0x43,0x3a,0xc8,0x83,0x1b,0xd8,0x43,0x39,0xc8,0xc3,0x3c,0xa4,0xc3,0x3b,0xb8, + 0xc3,0x94,0xc0,0x18,0x41,0x85,0x43,0x3a,0xc8,0x83,0x1b,0xb0,0x43,0x38,0xb8,0xc3, + 0x39,0xd4,0x43,0x38,0x9c,0x43,0x39,0xfc,0x82,0x3d,0x94,0x83,0x3c,0xcc,0x43,0x3a, + 0xbc,0x83,0x3b,0x4c,0x09,0x90,0x11,0x53,0x38,0xa4,0x83,0x3c,0xb8,0xc1,0x38,0xbc, + 0x43,0x3b,0xc0,0x43,0x3a,0xb0,0x43,0x39,0xfc,0xc2,0x3b,0xc0,0x03,0x3d,0xa4,0xc3, + 0x3b,0xb8,0xc3,0x3c,0x4c,0x19,0x14,0xc6,0x19,0xc1,0x84,0x43,0x3a,0xc8,0x83,0x1b, + 0x98,0x83,0x3c,0x84,0xc3,0x39,0xb4,0x43,0x39,0xb8,0x03,0x3d,0x4c,0x09,0xd6,0x00, + 0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c, + 0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07, + 0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42, + 0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83, + 0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07, + 0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70, + 0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3, + 0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2, + 0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc, + 0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68, + 0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07, + 0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72, + 0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec, + 0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc, + 0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8, + 0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03, + 0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c, + 0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74, + 0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4, + 0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc, + 0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1, + 0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50, + 0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51, + 0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21, + 0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06, + 0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c, + 0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77, + 0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec, + 0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8, + 0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84, + 0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03, + 0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87, + 0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f, + 0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3, + 0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2, + 0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21, + 0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83, + 0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87, + 0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e, + 0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90, + 0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85, + 0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00, + 0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f, + 0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20, + 0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x14,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x14,0x47,0x00,0x88,0x8d,0x00,0x90, + 0x1a,0x01,0xa8,0x01,0x12,0x33,0x00,0x14,0x66,0x00,0x08,0x8c,0x00,0x00,0x00,0x00, + 0x00,0x23,0x06,0xca,0x10,0x4c,0x09,0xb2,0x10,0x46,0x11,0x0c,0x32,0x04,0x03,0x62, + 0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x08,0x88,0x01,0x00,0x00,0x02,0x00,0x00, + 0x00,0x5b,0x06,0xe0,0x90,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float2 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + }; + + struct main0_in + { + float2 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]]) + { + main0_out out = {}; + out.gl_Position = float4(fma(in.position, float2(2.0, -2.0), float2(-1.0, 1.0)), 0.0, 1.0); + out.uv = in.texcoord0; + out.color = in.color0; + return out; + } + +*/ +static const uint8_t _sdtx_vs_source_metal_sim[577] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28, + 0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65, + 0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b, + 0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e, + 0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x32,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74, + 0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64, + 0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74, + 0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74, + 0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74, + 0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67, + 0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x66,0x6d,0x61,0x28,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x32,0x2e,0x30,0x2c, + 0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d, + 0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x29,0x2c,0x20,0x30,0x2e,0x30,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75, + 0x76,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20, + 0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float2 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv).xxxx * in.color; + return out; + } + +*/ +static const uint8_t _sdtx_fs_source_metal_sim[441] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65, + 0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65, + 0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d,0x70,0x2c,0x20,0x69,0x6e,0x2e, + 0x75,0x76,0x29,0x2e,0x78,0x78,0x78,0x78,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f, + 0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20, + 0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +/* + static float4 gl_Position; + static float2 position; + static float2 uv; + static float2 texcoord0; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float2 position : TEXCOORD0; + float2 texcoord0 : TEXCOORD1; + float4 color0 : TEXCOORD2; + }; + + struct SPIRV_Cross_Output + { + float2 uv : TEXCOORD0; + float4 color : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = float4(mad(position, float2(2.0f, -2.0f), float2(-1.0f, 1.0f)), 0.0f, 1.0f); + uv = texcoord0; + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + texcoord0 = stage_input.texcoord0; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.uv = uv; + stage_output.color = color; + return stage_output; + } +*/ +static const uint8_t _sdtx_vs_bytecode_hlsl4[692] = { + 0x44,0x58,0x42,0x43,0x07,0x05,0xa0,0xb3,0x53,0xc1,0x0a,0x0d,0x1e,0xf4,0xe4,0xa6, + 0x91,0xaf,0x4c,0xca,0x01,0x00,0x00,0x00,0xb4,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xe4,0x00,0x00,0x00,0x54,0x01,0x00,0x00, + 0x38,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, + 0x10,0x81,0x00,0x00,0x1c,0x00,0x00,0x00,0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66, + 0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65, + 0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00, + 0x49,0x53,0x47,0x4e,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x03,0x00,0x00, + 0x50,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0c,0x00,0x00,0x50,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x0f,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x54,0x45,0x58,0x43, + 0x4f,0x4f,0x52,0x44,0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x00,0xab,0xab,0xab,0x53,0x48,0x44,0x52,0xdc,0x00,0x00,0x00,0x40,0x00,0x01,0x00, + 0x37,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x5f,0x00,0x00,0x03, + 0xf2,0x10,0x10,0x00,0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0x32,0x20,0x10,0x00, + 0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00, + 0x67,0x00,0x00,0x04,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x36,0x00,0x00,0x05,0x32,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x01,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00, + 0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00,0x32,0x00,0x00,0x0f,0x32,0x20,0x10,0x00, + 0x02,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x02,0x40,0x00,0x00, + 0x00,0x00,0x00,0x40,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x02,0x40,0x00,0x00,0x00,0x00,0x80,0xbf,0x00,0x00,0x80,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x08,0xc2,0x20,0x10,0x00,0x02,0x00,0x00,0x00, + 0x02,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x80,0x3f,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, +}; +/* + Texture2D tex : register(t0); + SamplerState smp : register(s0); + + static float4 frag_color; + static float2 uv; + static float4 color; + + struct SPIRV_Cross_Input + { + float2 uv : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = tex.Sample(smp, uv).xxxx * color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + uv = stage_input.uv; + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +static const uint8_t _sdtx_fs_bytecode_hlsl4[608] = { + 0x44,0x58,0x42,0x43,0xb7,0xcd,0xbd,0xb1,0x6f,0x85,0x5d,0x59,0x07,0x7e,0xa3,0x6e, + 0xe2,0x23,0x68,0xa0,0x01,0x00,0x00,0x00,0x60,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x48,0x01,0x00,0x00, + 0xe4,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0x64,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c, + 0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c, + 0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00, + 0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54, + 0x61,0x72,0x67,0x65,0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0x94,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00, + 0x55,0x55,0x00,0x00,0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x62,0x10,0x00,0x03,0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00, + 0x45,0x00,0x00,0x09,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x06,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00, + 0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +/* + diagnostic(off, derivative_uniformity); + + var position_1 : vec2f; + + var uv : vec2f; + + var texcoord0 : vec2f; + + var color : vec4f; + + var color0 : vec4f; + + var gl_Position : vec4f; + + fn main_1() { + let x_19 : vec2f = position_1; + let x_27 : vec2f = ((x_19 * vec2f(2.0f, -2.0f)) + vec2f(-1.0f, 1.0f)); + gl_Position = vec4f(x_27.x, x_27.y, 0.0f, 1.0f); + let x_37 : vec2f = texcoord0; + uv = x_37; + let x_41 : vec4f = color0; + color = x_41; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + uv_1 : vec2f, + @location(1) + color_1 : vec4f, + } + + @vertex + fn main(@location(0) position_1_param : vec2f, @location(1) texcoord0_param : vec2f, @location(2) color0_param : vec4f) -> main_out { + position_1 = position_1_param; + texcoord0 = texcoord0_param; + color0 = color0_param; + main_1(); + return main_out(gl_Position, uv, color); + } + +*/ +static const uint8_t _sdtx_vs_source_wgsl[922] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70, + 0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65, + 0x3e,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74, + 0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a, + 0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20, + 0x7b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f, + 0x31,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x37,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x28,0x28,0x78,0x5f,0x31,0x39,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x32,0x2e,0x30,0x66,0x2c,0x20,0x2d,0x32,0x2e, + 0x30,0x66,0x29,0x29,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x2d,0x31,0x2e, + 0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x29,0x3b,0x0a,0x20,0x20,0x67,0x6c, + 0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x76,0x65,0x63,0x34, + 0x66,0x28,0x78,0x5f,0x32,0x37,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,0x37,0x2e,0x79, + 0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, + 0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x20, + 0x20,0x75,0x76,0x20,0x3d,0x20,0x78,0x5f,0x33,0x37,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x34,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x78,0x5f,0x34,0x31,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72, + 0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74, + 0x69,0x6e,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67, + 0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28, + 0x30,0x29,0x0a,0x20,0x20,0x75,0x76,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x32, + 0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31, + 0x29,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x28,0x30,0x29,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x74,0x65,0x78,0x63, + 0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65, + 0x63,0x32,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x32, + 0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x29,0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f, + 0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x5f,0x31,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x75,0x76,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var frag_color : vec4f; + + @group(1) @binding(64) var tex : texture_2d; + + @group(1) @binding(80) var smp : sampler; + + var uv : vec2f; + + var color : vec4f; + + fn main_1() { + let x_23 : vec2f = uv; + let x_24 : vec4f = textureSample(tex, smp, x_23); + let x_28 : vec4f = color; + frag_color = (vec4f(x_24.x, x_24.x, x_24.x, x_24.x) * x_28); + return; + } + + struct main_out { + @location(0) + frag_color_1 : vec4f, + } + + @fragment + fn main(@location(0) uv_param : vec2f, @location(1) color_param : vec4f) -> main_out { + uv = uv_param; + color = color_param; + main_1(); + return main_out(frag_color); + } +*/ +static const uint8_t _sdtx_fs_source_wgsl[663] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75, + 0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34, + 0x29,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x20,0x3a,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67, + 0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67, + 0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x73, + 0x6d,0x70,0x2c,0x20,0x78,0x5f,0x32,0x33,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74, + 0x20,0x78,0x5f,0x32,0x38,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x32, + 0x34,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,0x34,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32, + 0x34,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,0x34,0x2e,0x78,0x29,0x20,0x2a,0x20,0x78, + 0x5f,0x32,0x38,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f, + 0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x28,0x30,0x29,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40, + 0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20, + 0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x29,0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a, + 0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b, + 0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31, + 0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69, + 0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sdtx_vs_src_dummy = ""; +static const char* _sdtx_fs_src_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs +typedef struct { + uint32_t id; + sg_resource_state state; +} _sdtx_slot_t; + +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _sdtx_pool_t; + +typedef struct { + float x, y; +} _sdtx_float2_t; + +typedef struct { + float x, y; + uint16_t u, v; + uint32_t color; +} _sdtx_vertex_t; + +typedef struct { + int layer_id; + int first_vertex; + int num_vertices; +} _sdtx_command_t; + +typedef struct { + _sdtx_slot_t slot; + sdtx_context_desc_t desc; + uint32_t frame_id; + uint32_t update_frame_id; + struct { + int cap; + int next; + _sdtx_vertex_t* ptr; + } vertices; + struct { + int cap; + int next; + _sdtx_command_t* ptr; + } commands; + sg_buffer vbuf; + sg_pipeline pip; + int cur_font; + int cur_layer_id; + _sdtx_float2_t canvas_size; + _sdtx_float2_t glyph_size; + _sdtx_float2_t origin; + _sdtx_float2_t pos; + float tab_width; + uint32_t color; +} _sdtx_context_t; + +typedef struct { + _sdtx_pool_t pool; + _sdtx_context_t* contexts; +} _sdtx_context_pool_t; + +typedef struct { + uint32_t init_cookie; + sdtx_desc_t desc; + sg_image font_img; + sg_sampler font_smp; + sg_shader shader; + uint32_t fmt_buf_size; + char* fmt_buf; + sdtx_context def_ctx_id; + sdtx_context cur_ctx_id; + _sdtx_context_t* cur_ctx; // may be 0! + _sdtx_context_pool_t context_pool; + uint8_t font_pixels[SDTX_MAX_FONTS * 256 * 8 * 8]; +} _sdtx_t; +static _sdtx_t _sdtx; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SDTX_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sdtx_log_messages[] = { + _SDTX_LOG_ITEMS +}; +#undef _SDTX_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SDTX_PANIC(code) _sdtx_log(SDTX_LOGITEM_ ##code, 0, __LINE__) +#define _SDTX_ERROR(code) _sdtx_log(SDTX_LOGITEM_ ##code, 1, __LINE__) +#define _SDTX_WARN(code) _sdtx_log(SDTX_LOGITEM_ ##code, 2, __LINE__) +#define _SDTX_INFO(code) _sdtx_log(SDTX_LOGITEM_ ##code, 3, __LINE__) + +static void _sdtx_log(sdtx_log_item_t log_item, uint32_t log_level, uint32_t line_nr) { + if (_sdtx.desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _sdtx_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _sdtx.desc.logger.func("sdtx", log_level, (uint32_t)log_item, message, line_nr, filename, _sdtx.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +static void _sdtx_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _sdtx_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sdtx.desc.allocator.alloc_fn) { + ptr = _sdtx.desc.allocator.alloc_fn(size, _sdtx.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SDTX_PANIC(MALLOC_FAILED); + } + return ptr; +} + +static void* _sdtx_malloc_clear(size_t size) { + void* ptr = _sdtx_malloc(size); + _sdtx_clear(ptr, size); + return ptr; +} + +static void _sdtx_free(void* ptr) { + if (_sdtx.desc.allocator.free_fn) { + _sdtx.desc.allocator.free_fn(ptr, _sdtx.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██████ ██████ ███ ██ ████████ ███████ ██ ██ ████████ ██████ ██████ ██████ ██ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ █████ ███ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ████ ██ ███████ ██ ██ ██ ██ ██████ ██████ ███████ +// +// >>context pool +static void _sdtx_init_pool(_sdtx_pool_t* pool, int num) { + SOKOL_ASSERT(pool && (num >= 1)); + // slot 0 is reserved for the 'invalid id', so bump the pool size by 1 + pool->size = num + 1; + pool->queue_top = 0; + // generation counters indexable by pool slot index, slot 0 is reserved + size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; + pool->gen_ctrs = (uint32_t*) _sdtx_malloc_clear(gen_ctrs_size); + // it's not a bug to only reserve 'num' here + pool->free_queue = (int*) _sdtx_malloc_clear(sizeof(int) * (size_t)num); + // never allocate the zero-th pool item since the invalid id is 0 + for (int i = pool->size-1; i >= 1; i--) { + pool->free_queue[pool->queue_top++] = i; + } +} + +static void _sdtx_discard_pool(_sdtx_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _sdtx_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _sdtx_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +static int _sdtx_pool_alloc_index(_sdtx_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + if (pool->queue_top > 0) { + int slot_index = pool->free_queue[--pool->queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return slot_index; + } else { + // pool exhausted + return _SDTX_INVALID_SLOT_INDEX; + } +} + +static void _sdtx_pool_free_index(_sdtx_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SDTX_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + SOKOL_ASSERT(pool->queue_top < pool->size); + #ifdef SOKOL_DEBUG + // debug check against double-free + for (int i = 0; i < pool->queue_top; i++) { + SOKOL_ASSERT(pool->free_queue[i] != slot_index); + } + #endif + pool->free_queue[pool->queue_top++] = slot_index; + SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +static void _sdtx_setup_context_pool(const sdtx_desc_t* desc) { + SOKOL_ASSERT(desc); + // note: the pool will have an additional item, since slot 0 is reserved + SOKOL_ASSERT((desc->context_pool_size > 0) && (desc->context_pool_size < _SDTX_MAX_POOL_SIZE)); + _sdtx_init_pool(&_sdtx.context_pool.pool, desc->context_pool_size); + size_t pool_byte_size = sizeof(_sdtx_context_t) * (size_t)_sdtx.context_pool.pool.size; + _sdtx.context_pool.contexts = (_sdtx_context_t*) _sdtx_malloc_clear(pool_byte_size); +} + +static void _sdtx_discard_context_pool(void) { + SOKOL_ASSERT(_sdtx.context_pool.contexts); + _sdtx_free(_sdtx.context_pool.contexts); + _sdtx.context_pool.contexts = 0; + _sdtx_discard_pool(&_sdtx.context_pool.pool); +} + +/* allocate the slot at slot_index: + - bump the slot's generation counter + - create a resource id from the generation counter and slot index + - set the slot's id to this id + - set the slot's state to ALLOC + - return the resource id +*/ +static uint32_t _sdtx_slot_alloc(_sdtx_pool_t* pool, _sdtx_slot_t* slot, int slot_index) { + /* FIXME: add handling for an overflowing generation counter, + for now, just overflow (another option is to disable + the slot) + */ + SOKOL_ASSERT(pool && pool->gen_ctrs); + SOKOL_ASSERT((slot_index > _SDTX_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT((slot->state == SG_RESOURCESTATE_INITIAL) && (slot->id == SG_INVALID_ID)); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SDTX_SLOT_SHIFT)|(slot_index & _SDTX_SLOT_MASK); + slot->state = SG_RESOURCESTATE_ALLOC; + return slot->id; +} + +// extract slot index from id +static int _sdtx_slot_index(uint32_t id) { + int slot_index = (int) (id & _SDTX_SLOT_MASK); + SOKOL_ASSERT(_SDTX_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +// get context pointer without id-check +static _sdtx_context_t* _sdtx_context_at(uint32_t ctx_id) { + SOKOL_ASSERT(SG_INVALID_ID != ctx_id); + int slot_index = _sdtx_slot_index(ctx_id); + SOKOL_ASSERT((slot_index > _SDTX_INVALID_SLOT_INDEX) && (slot_index < _sdtx.context_pool.pool.size)); + return &_sdtx.context_pool.contexts[slot_index]; +} + +// get context pointer with id-check, returns 0 if no match +static _sdtx_context_t* _sdtx_lookup_context(uint32_t ctx_id) { + if (SG_INVALID_ID != ctx_id) { + _sdtx_context_t* ctx = _sdtx_context_at(ctx_id); + if (ctx->slot.id == ctx_id) { + return ctx; + } + } + return 0; +} + +// make context handle from raw uint32_t id +static sdtx_context _sdtx_make_ctx_id(uint32_t ctx_id) { + sdtx_context ctx; + ctx.id = ctx_id; + return ctx; +} + +static sdtx_context _sdtx_alloc_context(void) { + sdtx_context ctx_id; + int slot_index = _sdtx_pool_alloc_index(&_sdtx.context_pool.pool); + if (_SDTX_INVALID_SLOT_INDEX != slot_index) { + ctx_id = _sdtx_make_ctx_id(_sdtx_slot_alloc(&_sdtx.context_pool.pool, &_sdtx.context_pool.contexts[slot_index].slot, slot_index)); + } else { + // pool is exhausted + ctx_id = _sdtx_make_ctx_id(SG_INVALID_ID); + } + return ctx_id; +} + +static sdtx_context_desc_t _sdtx_context_desc_defaults(const sdtx_context_desc_t* desc) { + sdtx_context_desc_t res = *desc; + res.max_commands = _sdtx_def(res.max_commands, _SDTX_DEFAULT_MAX_COMMANDS); + res.char_buf_size = _sdtx_def(res.char_buf_size, _SDTX_DEFAULT_CHAR_BUF_SIZE); + res.canvas_width = _sdtx_def(res.canvas_width, _SDTX_DEFAULT_CANVAS_WIDTH); + res.canvas_height = _sdtx_def(res.canvas_height, _SDTX_DEFAULT_CANVAS_HEIGHT); + res.tab_width = _sdtx_def(res.tab_width, _SDTX_DEFAULT_TAB_WIDTH); + // keep pixel format attrs are passed as is into pipeline creation + SOKOL_ASSERT(res.char_buf_size > 0); + SOKOL_ASSERT(!isnan(res.canvas_width)); + SOKOL_ASSERT(!isnan(res.canvas_height)); + SOKOL_ASSERT(res.canvas_width > 0.0f); + SOKOL_ASSERT(res.canvas_height > 0.0f); + return res; +} + +static void _sdtx_set_layer(_sdtx_context_t* ctx, int layer_id); +static void _sdtx_rewind(_sdtx_context_t* ctx) { + SOKOL_ASSERT(ctx); + ctx->frame_id++; + ctx->vertices.next = 0; + ctx->commands.next = 0; + _sdtx_set_layer(ctx, 0); + ctx->cur_font = 0; + ctx->pos.x = 0.0f; + ctx->pos.y = 0.0f; +} + +static void _sdtx_commit_listener(void* userdata) { + _sdtx_context_t* ctx = _sdtx_lookup_context((uint32_t)(uintptr_t)userdata); + if (ctx) { + _sdtx_rewind(ctx); + } +} + +static sg_commit_listener _sdtx_make_commit_listener(_sdtx_context_t* ctx) { + sg_commit_listener listener = { _sdtx_commit_listener, (void*)(uintptr_t)(ctx->slot.id) }; + return listener; +} + +static void _sdtx_init_context(sdtx_context ctx_id, const sdtx_context_desc_t* in_desc) { + sg_push_debug_group("sokol-debugtext"); + + SOKOL_ASSERT((ctx_id.id != SG_INVALID_ID) && in_desc); + _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id); + SOKOL_ASSERT(ctx); + ctx->desc = _sdtx_context_desc_defaults(in_desc); + // NOTE: frame_id must be non-zero, so that updates trigger in first frame + ctx->frame_id = 1; + + ctx->vertices.cap = 6 * ctx->desc.char_buf_size; + const size_t vbuf_size = (size_t)ctx->vertices.cap * sizeof(_sdtx_vertex_t); + ctx->vertices.ptr = (_sdtx_vertex_t*) _sdtx_malloc(vbuf_size); + + ctx->commands.cap = ctx->desc.max_commands; + ctx->commands.ptr = (_sdtx_command_t*) _sdtx_malloc((size_t)ctx->commands.cap * sizeof(_sdtx_command_t)); + _sdtx_set_layer(ctx, 0); + + sg_buffer_desc vbuf_desc; + _sdtx_clear(&vbuf_desc, sizeof(vbuf_desc)); + vbuf_desc.size = vbuf_size; + vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vbuf_desc.usage = SG_USAGE_STREAM; + vbuf_desc.label = "sdtx-vbuf"; + ctx->vbuf = sg_make_buffer(&vbuf_desc); + SOKOL_ASSERT(SG_INVALID_ID != ctx->vbuf.id); + + sg_pipeline_desc pip_desc; + _sdtx_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.layout.buffers[0].stride = sizeof(_sdtx_vertex_t); + pip_desc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT2; + pip_desc.layout.attrs[1].format = SG_VERTEXFORMAT_USHORT2N; + pip_desc.layout.attrs[2].format = SG_VERTEXFORMAT_UBYTE4N; + pip_desc.shader = _sdtx.shader; + pip_desc.index_type = SG_INDEXTYPE_NONE; + pip_desc.sample_count = ctx->desc.sample_count; + pip_desc.depth.pixel_format = ctx->desc.depth_format; + pip_desc.colors[0].pixel_format = ctx->desc.color_format; + pip_desc.colors[0].blend.enabled = true; + pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + pip_desc.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + pip_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + pip_desc.label = "sdtx-pipeline"; + ctx->pip = sg_make_pipeline(&pip_desc); + SOKOL_ASSERT(SG_INVALID_ID != ctx->pip.id); + + ctx->canvas_size.x = ctx->desc.canvas_width; + ctx->canvas_size.y = ctx->desc.canvas_height; + ctx->glyph_size.x = 8.0f / ctx->canvas_size.x; + ctx->glyph_size.y = 8.0f / ctx->canvas_size.y; + ctx->tab_width = (float) ctx->desc.tab_width; + ctx->color = _SDTX_DEFAULT_COLOR; + + if (!sg_add_commit_listener(_sdtx_make_commit_listener(ctx))) { + _SDTX_ERROR(ADD_COMMIT_LISTENER_FAILED); + } + sg_pop_debug_group(); +} + +static void _sdtx_destroy_context(sdtx_context ctx_id) { + _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id); + if (ctx) { + if (ctx->vertices.ptr) { + _sdtx_free(ctx->vertices.ptr); + ctx->vertices.ptr = 0; + ctx->vertices.cap = 0; + ctx->vertices.next = 0; + } + if (ctx->commands.ptr) { + _sdtx_free(ctx->commands.ptr); + ctx->commands.ptr = 0; + ctx->commands.cap = 0; + ctx->commands.next = 0; + } + sg_push_debug_group("sokol_debugtext"); + sg_destroy_buffer(ctx->vbuf); + sg_destroy_pipeline(ctx->pip); + sg_remove_commit_listener(_sdtx_make_commit_listener(ctx)); + sg_pop_debug_group(); + _sdtx_clear(ctx, sizeof(*ctx)); + _sdtx_pool_free_index(&_sdtx.context_pool.pool, _sdtx_slot_index(ctx_id.id)); + } +} + +static bool _sdtx_is_default_context(sdtx_context ctx_id) { + return ctx_id.id == SDTX_DEFAULT_CONTEXT.id; +} + +// ███ ███ ██ ███████ ██████ +// ████ ████ ██ ██ ██ +// ██ ████ ██ ██ ███████ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ██████ +// +// >>misc + +// unpack linear 8x8 bits-per-pixel font data into 2D byte-per-pixel texture data +static void _sdtx_unpack_font(const sdtx_font_desc_t* font_desc, uint8_t* out_pixels) { + SOKOL_ASSERT(font_desc->data.ptr); + SOKOL_ASSERT((font_desc->data.size > 0) && ((font_desc->data.size % 8) == 0)); + SOKOL_ASSERT(font_desc->first_char <= font_desc->last_char); + SOKOL_ASSERT((size_t)(((font_desc->last_char - font_desc->first_char) + 1) * 8) == font_desc->data.size); + const uint8_t* ptr = (const uint8_t*) font_desc->data.ptr; + for (int chr = font_desc->first_char; chr <= font_desc->last_char; chr++) { + for (int line = 0; line < 8; line++) { + uint8_t bits = *ptr++; + for (int x = 0; x < 8; x++) { + out_pixels[line*256*8 + chr*8 + x] = ((bits>>(7-x)) & 1) ? 0xFF : 0x00; + } + } + } +} + +static void _sdtx_setup_common(void) { + + // common printf formatting buffer + _sdtx.fmt_buf_size = (uint32_t) _sdtx.desc.printf_buf_size + 1; + _sdtx.fmt_buf = (char*) _sdtx_malloc_clear(_sdtx.fmt_buf_size); + + sg_push_debug_group("sokol-debugtext"); + + // common shader for all contexts + sg_shader_desc shd_desc; + _sdtx_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.label = "sokol-debugtext-shader"; + shd_desc.attrs[0].glsl_name = "position"; + shd_desc.attrs[1].glsl_name = "texcoord0"; + shd_desc.attrs[2].glsl_name = "color0"; + shd_desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[0].hlsl_sem_index = 0; + shd_desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[1].hlsl_sem_index = 1; + shd_desc.attrs[2].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[2].hlsl_sem_index = 2; + shd_desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.images[0].hlsl_register_t_n = 0; + shd_desc.images[0].msl_texture_n = 0; + shd_desc.images[0].wgsl_group1_binding_n = 64; + shd_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.samplers[0].hlsl_register_s_n = 0; + shd_desc.samplers[0].msl_sampler_n = 0; + shd_desc.samplers[0].wgsl_group1_binding_n = 80; + shd_desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.image_sampler_pairs[0].image_slot = 0; + shd_desc.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.image_sampler_pairs[0].glsl_name = "tex_smp"; + #if defined(SOKOL_GLCORE) + shd_desc.vertex_func.source = (const char*)_sdtx_vs_source_glsl410; + shd_desc.fragment_func.source = (const char*)_sdtx_fs_source_glsl410; + #elif defined(SOKOL_GLES3) + shd_desc.vertex_func.source = (const char*)_sdtx_vs_source_glsl300es; + shd_desc.fragment_func.source = (const char*)_sdtx_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vertex_func.entry = "main0"; + shd_desc.fragment_func.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sdtx_vs_bytecode_metal_macos); + shd_desc.fragment_func.bytecode = SG_RANGE(_sdtx_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sdtx_vs_bytecode_metal_ios); + shd_desc.fragment_func.bytecode = SG_RANGE(_sdtx_fs_bytecode_metal_ios); + break; + default: + shd_desc.vertex_func.source = (const char*)_sdtx_vs_source_metal_sim; + shd_desc.fragment_func.source = (const char*)_sdtx_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vertex_func.bytecode = SG_RANGE(_sdtx_vs_bytecode_hlsl4); + shd_desc.fragment_func.bytecode = SG_RANGE(_sdtx_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vertex_func.source = (const char*)_sdtx_vs_source_wgsl; + shd_desc.fragment_func.source = (const char*)_sdtx_fs_source_wgsl; + #else + shd_desc.vertex_func.source = _sdtx_vs_src_dummy; + shd_desc.fragment_func.source = _sdtx_fs_src_dummy; + #endif + _sdtx.shader = sg_make_shader(&shd_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sdtx.shader.id); + + // unpack font data + memset(_sdtx.font_pixels, 0xFF, sizeof(_sdtx.font_pixels)); + const int unpacked_font_size = (int) (sizeof(_sdtx.font_pixels) / SDTX_MAX_FONTS); + for (int i = 0; i < SDTX_MAX_FONTS; i++) { + if (_sdtx.desc.fonts[i].data.ptr) { + _sdtx_unpack_font(&_sdtx.desc.fonts[i], &_sdtx.font_pixels[i * unpacked_font_size]); + } + } + + // create font texture and sampler + sg_image_desc img_desc; + _sdtx_clear(&img_desc, sizeof(img_desc)); + img_desc.width = 256 * 8; + img_desc.height = SDTX_MAX_FONTS * 8; + img_desc.pixel_format = SG_PIXELFORMAT_R8; + img_desc.data.subimage[0][0] = SG_RANGE(_sdtx.font_pixels); + img_desc.label = "sdtx-font-texture"; + _sdtx.font_img = sg_make_image(&img_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sdtx.font_img.id); + + sg_sampler_desc smp_desc; + _sdtx_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.min_filter = SG_FILTER_NEAREST; + smp_desc.mag_filter = SG_FILTER_NEAREST; + smp_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE; + smp_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE; + smp_desc.label = "sdtx-font-sampler"; + _sdtx.font_smp = sg_make_sampler(&smp_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sdtx.font_smp.id); + + sg_pop_debug_group(); +} + +static void _sdtx_discard_common(void) { + sg_push_debug_group("sokol-debugtext"); + sg_destroy_sampler(_sdtx.font_smp); + sg_destroy_image(_sdtx.font_img); + sg_destroy_shader(_sdtx.shader); + if (_sdtx.fmt_buf) { + _sdtx_free(_sdtx.fmt_buf); + _sdtx.fmt_buf = 0; + } + sg_pop_debug_group(); +} + +static uint32_t _sdtx_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return (uint32_t)(((uint32_t)a<<24)|((uint32_t)b<<16)|((uint32_t)g<<8)|r); +} + +static float _sdtx_clamp(float v, float lo, float hi) { + if (v < lo) return lo; + else if (v > hi) return hi; + else return v; +} + +static uint32_t _sdtx_pack_rgbaf(float r, float g, float b, float a) { + uint8_t r_u8 = (uint8_t) (_sdtx_clamp(r, 0.0f, 1.0f) * 255.0f); + uint8_t g_u8 = (uint8_t) (_sdtx_clamp(g, 0.0f, 1.0f) * 255.0f); + uint8_t b_u8 = (uint8_t) (_sdtx_clamp(b, 0.0f, 1.0f) * 255.0f); + uint8_t a_u8 = (uint8_t) (_sdtx_clamp(a, 0.0f, 1.0f) * 255.0f); + return _sdtx_pack_rgbab(r_u8, g_u8, b_u8, a_u8); +} + +static void _sdtx_ctrl_char(_sdtx_context_t* ctx, uint8_t c) { + switch (c) { + case '\r': + ctx->pos.x = 0.0f; + break; + case '\n': + ctx->pos.x = 0.0f; + ctx->pos.y += 1.0f; + break; + case '\t': + ctx->pos.x = (ctx->pos.x - fmodf(ctx->pos.x, ctx->tab_width)) + ctx->tab_width; + break; + case ' ': + ctx->pos.x += 1.0f; + break; + } +} + +static _sdtx_vertex_t* _sdtx_next_vertex(_sdtx_context_t* ctx) { + if ((ctx->vertices.next + 6) <= ctx->vertices.cap) { + _sdtx_vertex_t* vx = &ctx->vertices.ptr[ctx->vertices.next]; + ctx->vertices.next += 6; + return vx; + } else { + return 0; + } +} + +static _sdtx_command_t* _sdtx_cur_command(_sdtx_context_t* ctx) { + if (ctx->commands.next > 0) { + return &ctx->commands.ptr[ctx->commands.next - 1]; + } else { + return 0; + } +} + +static _sdtx_command_t* _sdtx_next_command(_sdtx_context_t* ctx) { + if (ctx->commands.next < ctx->commands.cap) { + return &ctx->commands.ptr[ctx->commands.next++]; + } else { + _SDTX_ERROR(COMMAND_BUFFER_FULL); + return 0; + } +} + +static void _sdtx_set_layer(_sdtx_context_t* ctx, int layer_id) { + ctx->cur_layer_id = layer_id; + _sdtx_command_t* cur_cmd = _sdtx_cur_command(ctx); + if (cur_cmd) { + if ((cur_cmd->num_vertices == 0) || (cur_cmd->layer_id == layer_id)) { + // no vertices recorded in current draw command, or layer hasn't changed, can just reuse this + cur_cmd->layer_id = layer_id; + } else { + // layer has changed, need to start a new draw command + _sdtx_command_t* next_cmd = _sdtx_next_command(ctx); + if (next_cmd) { + next_cmd->layer_id = layer_id; + next_cmd->first_vertex = cur_cmd->first_vertex + cur_cmd->num_vertices; + next_cmd->num_vertices = 0; + } + } + } else { + // first draw command in frame + _sdtx_command_t* next_cmd = _sdtx_next_command(ctx); + if (next_cmd) { + next_cmd->layer_id = layer_id; + next_cmd->first_vertex = 0; + next_cmd->num_vertices = 0; + } + } +} + +static void _sdtx_render_char(_sdtx_context_t* ctx, uint8_t c) { + _sdtx_vertex_t* vx = _sdtx_next_vertex(ctx); + _sdtx_command_t* cmd = _sdtx_cur_command(ctx); + if (vx && cmd) { + // update vertex count in current draw command + cmd->num_vertices += 6; + + const float x0 = (ctx->origin.x + ctx->pos.x) * ctx->glyph_size.x; + const float y0 = (ctx->origin.y + ctx->pos.y) * ctx->glyph_size.y; + const float x1 = x0 + ctx->glyph_size.x; + const float y1 = y0 + ctx->glyph_size.y; + + // glyph width and height in font texture space + // NOTE: the '+1' and '-2' fixes texture bleeding into the neighboring font texture cell + const uint16_t uvw = 0x10000 / 0x100; + const uint16_t uvh = 0x10000 / SDTX_MAX_FONTS; + const uint16_t u0 = (((uint16_t)c) * uvw) + 1; + const uint16_t v0 = (((uint16_t)ctx->cur_font) * uvh) + 1; + uint16_t u1 = (u0 + uvw) - 2; + uint16_t v1 = (v0 + uvh) - 2; + const uint32_t color = ctx->color; + + // write 6 vertices + vx->x=x0; vx->y=y0; vx->u = u0; vx->v = v0; vx->color = color; vx++; + vx->x=x1; vx->y=y0; vx->u = u1; vx->v = v0; vx->color = color; vx++; + vx->x=x1; vx->y=y1; vx->u = u1; vx->v = v1; vx->color = color; vx++; + + vx->x=x0; vx->y=y0; vx->u = u0; vx->v = v0; vx->color = color; vx++; + vx->x=x1; vx->y=y1; vx->u = u1; vx->v = v1; vx->color = color; vx++; + vx->x=x0; vx->y=y1; vx->u = u0; vx->v = v1; vx->color = color; vx++; + } + ctx->pos.x += 1.0f; +} + +static void _sdtx_put_char(_sdtx_context_t* ctx, char c) { + uint8_t c_u8 = (uint8_t)c; + if (c_u8 <= 32) { + _sdtx_ctrl_char(ctx, c_u8); + } else { + _sdtx_render_char(ctx, c_u8); + } +} + +SOKOL_API_IMPL void _sdtx_draw_layer(_sdtx_context_t* ctx, int layer_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + SOKOL_ASSERT(ctx); + if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) { + sg_push_debug_group("sokol-debugtext"); + + if (ctx->update_frame_id != ctx->frame_id) { + ctx->update_frame_id = ctx->frame_id; + const sg_range range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sdtx_vertex_t) }; + sg_update_buffer(ctx->vbuf, &range); + } + + sg_apply_pipeline(ctx->pip); + sg_bindings bindings; + _sdtx_clear(&bindings, sizeof(bindings)); + bindings.vertex_buffers[0] = ctx->vbuf; + bindings.images[0] = _sdtx.font_img; + bindings.samplers[0] = _sdtx.font_smp; + sg_apply_bindings(&bindings); + for (int cmd_index = 0; cmd_index < ctx->commands.next; cmd_index++) { + const _sdtx_command_t* cmd = &ctx->commands.ptr[cmd_index]; + if (cmd->layer_id != layer_id) { + continue; + } + SOKOL_ASSERT((cmd->num_vertices % 6) == 0); + sg_draw(cmd->first_vertex, cmd->num_vertices, 1); + } + sg_pop_debug_group(); + } +} + + +static sdtx_desc_t _sdtx_desc_defaults(const sdtx_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sdtx_desc_t res = *desc; + res.context_pool_size = _sdtx_def(res.context_pool_size, _SDTX_DEFAULT_CONTEXT_POOL_SIZE); + res.printf_buf_size = _sdtx_def(res.printf_buf_size, _SDTX_DEFAULT_PRINTF_BUF_SIZE); + for (int i = 0; i < SDTX_MAX_FONTS; i++) { + if (res.fonts[i].data.ptr) { + res.fonts[i].last_char = _sdtx_def(res.fonts[i].last_char, 255); + } + } + res.context = _sdtx_context_desc_defaults(&res.context); + SOKOL_ASSERT(res.context_pool_size > 0); + SOKOL_ASSERT(res.printf_buf_size > 0); + SOKOL_ASSERT(res.context.char_buf_size > 0); + return res; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sdtx_setup(const sdtx_desc_t* desc) { + SOKOL_ASSERT(desc); + _sdtx_clear(&_sdtx, sizeof(_sdtx)); + _sdtx.init_cookie = _SDTX_INIT_COOKIE; + _sdtx.desc = _sdtx_desc_defaults(desc); + _sdtx_setup_context_pool(&_sdtx.desc); + _sdtx_setup_common(); + _sdtx.def_ctx_id = sdtx_make_context(&_sdtx.desc.context); + SOKOL_ASSERT(SDTX_DEFAULT_CONTEXT.id == _sdtx.def_ctx_id.id); + sdtx_set_context(_sdtx.def_ctx_id); +} + +SOKOL_API_IMPL void sdtx_shutdown(void) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + for (int i = 0; i < _sdtx.context_pool.pool.size; i++) { + _sdtx_context_t* ctx = &_sdtx.context_pool.contexts[i]; + _sdtx_destroy_context(_sdtx_make_ctx_id(ctx->slot.id)); + } + _sdtx_discard_common(); + _sdtx_discard_context_pool(); + _sdtx.init_cookie = 0; +} + +SOKOL_API_IMPL sdtx_font_desc_t sdtx_font_kc853(void) { + sdtx_font_desc_t desc = { { _sdtx_font_kc853, sizeof(_sdtx_font_kc853) }, 0, 255 }; + return desc; +} + +SOKOL_API_IMPL sdtx_font_desc_t sdtx_font_kc854(void) { + sdtx_font_desc_t desc = { { _sdtx_font_kc854, sizeof(_sdtx_font_kc854) }, 0, 255 }; + return desc; +} + +SOKOL_API_IMPL sdtx_font_desc_t sdtx_font_z1013(void) { + sdtx_font_desc_t desc = { { _sdtx_font_z1013, sizeof(_sdtx_font_z1013) }, 0, 255 }; + return desc; +} + +SOKOL_API_IMPL sdtx_font_desc_t sdtx_font_cpc(void) { + sdtx_font_desc_t desc = { { _sdtx_font_cpc, sizeof(_sdtx_font_cpc) }, 0, 255 }; + return desc; +} + +SOKOL_API_IMPL sdtx_font_desc_t sdtx_font_c64(void) { + sdtx_font_desc_t desc = { { _sdtx_font_c64, sizeof(_sdtx_font_c64) }, 0, 255 }; + return desc; +} + +SOKOL_API_IMPL sdtx_font_desc_t sdtx_font_oric(void) { + sdtx_font_desc_t desc = { { _sdtx_font_oric, sizeof(_sdtx_font_oric) }, 0, 255 }; + return desc; +} + +SOKOL_API_IMPL sdtx_context sdtx_make_context(const sdtx_context_desc_t* desc) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + SOKOL_ASSERT(desc); + sdtx_context ctx_id = _sdtx_alloc_context(); + if (ctx_id.id != SG_INVALID_ID) { + _sdtx_init_context(ctx_id, desc); + } else { + _SDTX_ERROR(CONTEXT_POOL_EXHAUSTED); + } + return ctx_id; +} + +SOKOL_API_IMPL void sdtx_destroy_context(sdtx_context ctx_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + if (_sdtx_is_default_context(ctx_id)) { + _SDTX_ERROR(CANNOT_DESTROY_DEFAULT_CONTEXT); + return; + } + _sdtx_destroy_context(ctx_id); + // re-validate the current context pointer (this will return a nullptr + // if we just destroyed the current context) + _sdtx.cur_ctx = _sdtx_lookup_context(_sdtx.cur_ctx_id.id); +} + +SOKOL_API_IMPL void sdtx_set_context(sdtx_context ctx_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + if (_sdtx_is_default_context(ctx_id)) { + _sdtx.cur_ctx_id = _sdtx.def_ctx_id; + } else { + _sdtx.cur_ctx_id = ctx_id; + } + // this may return a nullptr if the ctx_id handle is invalid + _sdtx.cur_ctx = _sdtx_lookup_context(_sdtx.cur_ctx_id.id); +} + +SOKOL_API_IMPL sdtx_context sdtx_get_context(void) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + return _sdtx.cur_ctx_id; +} + +SOKOL_API_IMPL sdtx_context sdtx_default_context(void) { + return SDTX_DEFAULT_CONTEXT; +} + +SOKOL_API_IMPL void sdtx_layer(int layer_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + _sdtx_set_layer(ctx, layer_id); + } +} + +SOKOL_API_IMPL void sdtx_font(int font_index) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + SOKOL_ASSERT((font_index >= 0) && (font_index < SDTX_MAX_FONTS)); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->cur_font = font_index; + } +} + +SOKOL_API_IMPL void sdtx_canvas(float w, float h) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + SOKOL_ASSERT(!isnan(w)); + SOKOL_ASSERT(!isnan(h)); + SOKOL_ASSERT((w > 0.0f) && (h > 0.0f)); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->canvas_size.x = w; + ctx->canvas_size.y = h; + ctx->glyph_size.x = (8.0f / ctx->canvas_size.x); + ctx->glyph_size.y = (8.0f / ctx->canvas_size.y); + ctx->origin.x = 0.0f; + ctx->origin.y = 0.0f; + ctx->pos.x = 0.0f; + ctx->pos.y = 0.0f; + } +} + +SOKOL_API_IMPL void sdtx_origin(float x, float y) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->origin.x = x; + ctx->origin.y = y; + } +} + +SOKOL_API_IMPL void sdtx_home(void) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.x = 0.0f; + ctx->pos.y = 0.0f; + } +} + +SOKOL_API_IMPL void sdtx_pos(float x, float y) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.x = x; + ctx->pos.y = y; + } +} + +SOKOL_API_IMPL void sdtx_pos_x(float x) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.x = x; + } +} + +SOKOL_API_IMPL void sdtx_pos_y(float y) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.y = y; + } +} + +SOKOL_API_IMPL void sdtx_move(float dx, float dy) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.x += dx; + ctx->pos.y += dy; + } +} + +SOKOL_API_IMPL void sdtx_move_x(float dx) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.x += dx; + } +} + +SOKOL_API_IMPL void sdtx_move_y(float dy) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.y += dy; + } +} + +SOKOL_API_IMPL void sdtx_crlf(void) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->pos.x = 0.0f; + ctx->pos.y += 1.0f; + } +} + +SOKOL_API_IMPL void sdtx_color3b(uint8_t r, uint8_t g, uint8_t b) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->color = _sdtx_pack_rgbab(r, g, b, 255); + } +} + +SOKOL_API_IMPL void sdtx_color3f(float r, float g, float b) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->color = _sdtx_pack_rgbaf(r, g, b, 1.0f); + } +} + +SOKOL_API_IMPL void sdtx_color4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->color = _sdtx_pack_rgbab(r, g, b, a); + } +} + +SOKOL_API_IMPL void sdtx_color4f(float r, float g, float b, float a) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->color = _sdtx_pack_rgbaf(r, g, b, a); + } +} + +SOKOL_API_IMPL void sdtx_color1i(uint32_t rgba) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + ctx->color = rgba; + } +} + +SOKOL_DEBUGTEXT_API_DECL void sdtx_putc(char chr) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + _sdtx_put_char(ctx, chr); + } +} + +SOKOL_DEBUGTEXT_API_DECL void sdtx_puts(const char* str) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + char chr; + while (0 != (chr = *str++)) { + _sdtx_put_char(ctx, chr); + } + } +} + +SOKOL_DEBUGTEXT_API_DECL void sdtx_putr(const char* str, int len) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + for (int i = 0; i < len; i++) { + char chr = str[i]; + if (0 == chr) { + break; + } + _sdtx_put_char(ctx, chr); + } + } +} + +SOKOL_DEBUGTEXT_API_DECL int sdtx_vprintf(const char* fmt, va_list args) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + SOKOL_ASSERT(_sdtx.fmt_buf && (_sdtx.fmt_buf_size >= 2)); + int res = SOKOL_VSNPRINTF(_sdtx.fmt_buf, _sdtx.fmt_buf_size, fmt, args); + // make sure we're 0-terminated in case we're on an old MSVC + _sdtx.fmt_buf[_sdtx.fmt_buf_size-1] = 0; + sdtx_puts(_sdtx.fmt_buf); + return res; +} + +SOKOL_DEBUGTEXT_API_DECL int sdtx_printf(const char* fmt, ...) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + SOKOL_ASSERT(_sdtx.fmt_buf && (_sdtx.fmt_buf_size >= 2)); + va_list args; + va_start(args, fmt); + int res = SOKOL_VSNPRINTF(_sdtx.fmt_buf, _sdtx.fmt_buf_size, fmt, args); + va_end(args); + // make sure we're 0-terminated in case we're on an old MSVC + _sdtx.fmt_buf[_sdtx.fmt_buf_size-1] = 0; + sdtx_puts(_sdtx.fmt_buf); + return res; +} + +SOKOL_API_IMPL void sdtx_draw(void) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + _sdtx_draw_layer(ctx, 0); + } +} + +SOKOL_API_IMPL void sdtx_context_draw(sdtx_context ctx_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id); + if (ctx) { + _sdtx_draw_layer(ctx, 0); + } +} + +SOKOL_API_IMPL void sdtx_draw_layer(int layer_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx.cur_ctx; + if (ctx) { + _sdtx_draw_layer(ctx, layer_id); + } +} + +SOKOL_API_IMPL void sdtx_context_draw_layer(sdtx_context ctx_id, int layer_id) { + SOKOL_ASSERT(_SDTX_INIT_COOKIE == _sdtx.init_cookie); + _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id); + if (ctx) { + _sdtx_draw_layer(ctx, layer_id); + } +} +#endif // SOKOL_DEBUGTEXT_IMPL diff --git a/modules/sokol-jai/sokol/c/sokol_defines.h b/modules/sokol-jai/sokol/c/sokol_defines.h new file mode 100644 index 0000000..41f0c95 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_defines.h @@ -0,0 +1,8 @@ +#define SOKOL_NO_ENTRY +#if defined(_WIN32) + #define SOKOL_WIN32_FORCE_MAIN +#endif +// FIXME: macOS Zig HACK without this, some C stdlib headers throw errors +#if defined(__APPLE__) +#include +#endif diff --git a/modules/sokol-jai/sokol/c/sokol_fetch.c b/modules/sokol-jai/sokol/c/sokol_fetch.c new file mode 100644 index 0000000..526bf25 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_fetch.c @@ -0,0 +1,5 @@ +#if defined(IMPL) +#define SOKOL_FETCH_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_fetch.h" diff --git a/modules/sokol-jai/sokol/c/sokol_fetch.h b/modules/sokol-jai/sokol/c/sokol_fetch.h new file mode 100644 index 0000000..ad7d993 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_fetch.h @@ -0,0 +1,2810 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_FETCH_IMPL) +#define SOKOL_FETCH_IMPL +#endif +#ifndef SOKOL_FETCH_INCLUDED +/* + sokol_fetch.h -- asynchronous data loading/streaming + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_FETCH_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_FETCH_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_FETCH_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SFETCH_MAX_PATH - max length of UTF-8 filesystem path / URL (default: 1024 bytes) + SFETCH_MAX_USERDATA_UINT64 - max size of embedded userdata in number of uint64_t, userdata + will be copied into an 8-byte aligned memory region associated + with each in-flight request, default value is 16 (== 128 bytes) + SFETCH_MAX_CHANNELS - max number of IO channels (default is 16, also see sfetch_desc_t.num_channels) + + If sokol_fetch.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_FETCH_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + NOTE: The following documentation talks a lot about "IO threads". Actual + threads are only used on platforms where threads are available. The web + version (emscripten/wasm) doesn't use POSIX-style threads, but instead + asynchronous Javascript calls chained together by callbacks. The actual + source code differences between the two approaches have been kept to + a minimum though. + + FEATURE OVERVIEW + ================ + + - Asynchronously load complete files, or stream files incrementally via + HTTP (on web platform), or the local file system (on native platforms) + + - Request / response-callback model, user code sends a request + to initiate a file-load, sokol_fetch.h calls the response callback + on the same thread when data is ready or user-code needs + to respond otherwise + + - Not limited to the main-thread or a single thread: A sokol-fetch + "context" can live on any thread, and multiple contexts + can operate side-by-side on different threads. + + - Memory management for data buffers is under full control of user code. + sokol_fetch.h won't allocate memory after it has been setup. + + - Automatic rate-limiting guarantees that only a maximum number of + requests is processed at any one time, allowing a zero-allocation + model, where all data is streamed into fixed-size, pre-allocated + buffers. + + - Active Requests can be paused, continued and cancelled from anywhere + in the user-thread which sent this request. + + + TL;DR EXAMPLE CODE + ================== + This is the most-simple example code to load a single data file with a + known maximum size: + + (1) initialize sokol-fetch with default parameters (but NOTE that the + default setup parameters provide a safe-but-slow "serialized" + operation). In order to see any logging output in case or errors + you should always provide a logging function + (such as 'slog_func' from sokol_log.h): + + sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func }); + + (2) send a fetch-request to load a file from the current directory + into a buffer big enough to hold the entire file content: + + static uint8_t buf[MAX_FILE_SIZE]; + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = response_callback, + .buffer = { + .ptr = buf, + .size = sizeof(buf) + } + }); + + If 'buf' is a value (e.g. an array or struct item), the .buffer item can + be initialized with the SFETCH_RANGE() helper macro: + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = response_callback, + .buffer = SFETCH_RANGE(buf) + }); + + (3) write a 'response-callback' function, this will be called whenever + the user-code must respond to state changes of the request + (most importantly when data has been loaded): + + void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // data has been loaded, and is available via the + // sfetch_range_t struct item 'data': + const void* ptr = response->data.ptr; + size_t num_bytes = response->data.size; + } + if (response->finished) { + // the 'finished'-flag is the catch-all flag for when the request + // is finished, no matter if loading was successful or failed, + // so any cleanup-work should happen here... + ... + if (response->failed) { + // 'failed' is true in (addition to 'finished') if something + // went wrong (file doesn't exist, or less bytes could be + // read from the file than expected) + } + } + } + + (4) pump the sokol-fetch message queues, and invoke response callbacks + by calling: + + sfetch_dowork(); + + In an event-driven app this should be called in the event loop. If you + use sokol-app this would be in your frame_cb function. + + (5) finally, call sfetch_shutdown() at the end of the application: + + There's many other loading-scenarios, for instance one doesn't have to + provide a buffer upfront, this can also happen in the response callback. + + Or it's possible to stream huge files into small fixed-size buffer, + complete with pausing and continuing the download. + + It's also possible to improve the 'pipeline throughput' by fetching + multiple files in parallel, but at the same time limit the maximum + number of requests that can be 'in-flight'. + + For how this all works, please read the following documentation sections :) + + + API DOCUMENTATION + ================= + + void sfetch_setup(const sfetch_desc_t* desc) + -------------------------------------------- + First call sfetch_setup(const sfetch_desc_t*) on any thread before calling + any other sokol-fetch functions on the same thread. + + sfetch_setup() takes a pointer to an sfetch_desc_t struct with setup + parameters. Parameters which should use their default values must + be zero-initialized: + + - max_requests (uint32_t): + The maximum number of requests that can be alive at any time, the + default is 128. + + - num_channels (uint32_t): + The number of "IO channels" used to parallelize and prioritize + requests, the default is 1. + + - num_lanes (uint32_t): + The number of "lanes" on a single channel. Each request which is + currently 'inflight' on a channel occupies one lane until the + request is finished. This is used for automatic rate-limiting + (search below for CHANNELS AND LANES for more details). The + default number of lanes is 1. + + For example, to setup sokol-fetch for max 1024 active requests, 4 channels, + and 8 lanes per channel in C99: + + sfetch_setup(&(sfetch_desc_t){ + .max_requests = 1024, + .num_channels = 4, + .num_lanes = 8 + }); + + sfetch_setup() is the only place where sokol-fetch will allocate memory. + + NOTE that the default setup parameters of 1 channel and 1 lane per channel + has a very poor 'pipeline throughput' since this essentially serializes + IO requests (a new request will only be processed when the last one has + finished), and since each request needs at least one roundtrip between + the user- and IO-thread the throughput will be at most one request per + frame. Search for LATENCY AND THROUGHPUT below for more information on + how to increase throughput. + + NOTE that you can call sfetch_setup() on multiple threads, each thread + will get its own thread-local sokol-fetch instance, which will work + independently from sokol-fetch instances on other threads. + + void sfetch_shutdown(void) + -------------------------- + Call sfetch_shutdown() at the end of the application to stop any + IO threads and free all memory that was allocated in sfetch_setup(). + + sfetch_handle_t sfetch_send(const sfetch_request_t* request) + ------------------------------------------------------------ + Call sfetch_send() to start loading data, the function takes a pointer to an + sfetch_request_t struct with request parameters and returns a + sfetch_handle_t identifying the request for later calls. At least + a path/URL and callback must be provided: + + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = my_response_callback + }); + + sfetch_send() will return an invalid handle if no request can be allocated + from the internal pool because all available request items are 'in-flight'. + + The sfetch_request_t struct contains the following parameters (optional + parameters that are not provided must be zero-initialized): + + - path (const char*, required) + Pointer to an UTF-8 encoded C string describing the filesystem + path or HTTP URL. The string will be copied into an internal data + structure, and passed "as is" (apart from any required + encoding-conversions) to fopen(), CreateFileW() or + the html fetch API call. The maximum length of the string is defined by + the SFETCH_MAX_PATH configuration define, the default is 1024 bytes + including the 0-terminator byte. + + - callback (sfetch_callback_t, required) + Pointer to a response-callback function which is called when the + request needs "user code attention". Search below for REQUEST + STATES AND THE RESPONSE CALLBACK for detailed information about + handling responses in the response callback. + + - channel (uint32_t, optional) + Index of the IO channel where the request should be processed. + Channels are used to parallelize and prioritize requests relative + to each other. Search below for CHANNELS AND LANES for more + information. The default channel is 0. + + - chunk_size (uint32_t, optional) + The chunk_size member is used for streaming data incrementally + in small chunks. After 'chunk_size' bytes have been loaded into + to the streaming buffer, the response callback will be called + with the buffer containing the fetched data for the current chunk. + If chunk_size is 0 (the default), than the whole file will be loaded. + Please search below for CHUNK SIZE AND HTTP COMPRESSION for + important information how streaming works if the web server + is serving compressed data. + + - buffer (sfetch_range_t) + This is a optional pointer/size pair describing a chunk of memory where + data will be loaded into (if no buffer is provided upfront, this + must happen in the response callback). If a buffer is provided, + it must be big enough to either hold the entire file (if chunk_size + is zero), or the *uncompressed* data for one downloaded chunk + (if chunk_size is > 0). + + - user_data (sfetch_range_t) + The user_data ptr/size range struct describe an optional POD blob + (plain-old-data) associated with the request which will be copied(!) + into an internal memory block. The maximum default size of this + memory block is 128 bytes (but can be overridden by defining + SFETCH_MAX_USERDATA_UINT64 before including the notification, note + that this define is in "number of uint64_t", not number of bytes). + The user-data block is 8-byte aligned, and will be copied via + memcpy() (so don't put any C++ "smart members" in there). + + NOTE that request handles are strictly thread-local and only unique + within the thread the handle was created on, and all function calls + involving a request handle must happen on that same thread. + + bool sfetch_handle_valid(sfetch_handle_t request) + ------------------------------------------------- + This checks if the provided request handle is valid, and is associated with + a currently active request. It will return false if: + + - sfetch_send() returned an invalid handle because it couldn't allocate + a new request from the internal request pool (because they're all + in flight) + - the request associated with the handle is no longer alive (because + it either finished successfully, or the request failed for some + reason) + + void sfetch_dowork(void) + ------------------------ + Call sfetch_dowork(void) in regular intervals (for instance once per frame) + on the same thread as sfetch_setup() to "turn the gears". If you are sending + requests but never hear back from them in the response callback function, then + the most likely reason is that you forgot to add the call to sfetch_dowork() + in the per-frame function. + + sfetch_dowork() roughly performs the following work: + + - any new requests that have been sent with sfetch_send() since the + last call to sfetch_dowork() will be dispatched to their IO channels + and assigned a free lane. If all lanes on that channel are occupied + by requests 'in flight', incoming requests must wait until + a lane becomes available + + - for all new requests which have been enqueued on a channel which + don't already have a buffer assigned the response callback will be + called with (response->dispatched == true) so that the response + callback can inspect the dynamically assigned lane and bind a buffer + to the request (search below for CHANNELS AND LANE for more info) + + - a state transition from "user side" to "IO thread side" happens for + each new request that has been dispatched to a channel. + + - requests dispatched to a channel are either forwarded into that + channel's worker thread (on native platforms), or cause an HTTP + request to be sent via an asynchronous fetch() call (on the web + platform) + + - for all requests which have finished their current IO operation a + state transition from "IO thread side" to "user side" happens, + and the response callback is called so that the fetched data + can be processed. + + - requests which are completely finished (either because the entire + file content has been loaded, or they are in the FAILED state) are + freed (this just changes their state in the 'request pool', no actual + memory is freed) + + - requests which are not yet finished are fed back into the + 'incoming' queue of their channel, and the cycle starts again, this + only happens for requests which perform data streaming (not load + the entire file at once). + + void sfetch_cancel(sfetch_handle_t request) + ------------------------------------------- + This cancels a request in the next sfetch_dowork() call and invokes the + response callback with (response.failed == true) and (response.finished + == true) to give user-code a chance to do any cleanup work for the + request. If sfetch_cancel() is called for a request that is no longer + alive, nothing bad will happen (the call will simply do nothing). + + void sfetch_pause(sfetch_handle_t request) + ------------------------------------------ + This pauses an active request in the next sfetch_dowork() call and puts + it into the PAUSED state. For all requests in PAUSED state, the response + callback will be called in each call to sfetch_dowork() to give user-code + a chance to CONTINUE the request (by calling sfetch_continue()). Pausing + a request makes sense for dynamic rate-limiting in streaming scenarios + (like video/audio streaming with a fixed number of streaming buffers. As + soon as all available buffers are filled with download data, downloading + more data must be prevented to allow video/audio playback to catch up and + free up empty buffers for new download data. + + void sfetch_continue(sfetch_handle_t request) + --------------------------------------------- + Continues a paused request, counterpart to the sfetch_pause() function. + + void sfetch_bind_buffer(sfetch_handle_t request, sfetch_range_t buffer) + ---------------------------------------------------------------------------------------- + This "binds" a new buffer (as pointer/size pair) to an active request. The + function *must* be called from inside the response-callback, and there + must not already be another buffer bound. + + void* sfetch_unbind_buffer(sfetch_handle_t request) + --------------------------------------------------- + This removes the current buffer binding from the request and returns + a pointer to the previous buffer (useful if the buffer was dynamically + allocated and it must be freed). + + sfetch_unbind_buffer() *must* be called from inside the response callback. + + The usual code sequence to bind a different buffer in the response + callback might look like this: + + void response_callback(const sfetch_response_t* response) { + if (response.fetched) { + ... + // switch to a different buffer (in the FETCHED state it is + // guaranteed that the request has a buffer, otherwise it + // would have gone into the FAILED state + void* old_buf_ptr = sfetch_unbind_buffer(response.handle); + free(old_buf_ptr); + void* new_buf_ptr = malloc(new_buf_size); + sfetch_bind_buffer(response.handle, new_buf_ptr, new_buf_size); + } + if (response.finished) { + // unbind and free the currently associated buffer, + // the buffer pointer could be null if the request has failed + // NOTE that it is legal to call free() with a nullptr, + // this happens if the request failed to open its file + // and never goes into the OPENED state + void* buf_ptr = sfetch_unbind_buffer(response.handle); + free(buf_ptr); + } + } + + sfetch_desc_t sfetch_desc(void) + ------------------------------- + sfetch_desc() returns a copy of the sfetch_desc_t struct passed to + sfetch_setup(), with zero-initialized values replaced with + their default values. + + int sfetch_max_userdata_bytes(void) + ----------------------------------- + This returns the value of the SFETCH_MAX_USERDATA_UINT64 config + define, but in number of bytes (so SFETCH_MAX_USERDATA_UINT64*8). + + int sfetch_max_path(void) + ------------------------- + Returns the value of the SFETCH_MAX_PATH config define. + + + REQUEST STATES AND THE RESPONSE CALLBACK + ======================================== + A request goes through a number of states during its lifetime. Depending + on the current state of a request, it will be 'owned' either by the + "user-thread" (where the request was sent) or an IO thread. + + You can think of a request as "ping-ponging" between the IO thread and + user thread, any actual IO work is done on the IO thread, while + invocations of the response-callback happen on the user-thread. + + All state transitions and callback invocations happen inside the + sfetch_dowork() function. + + An active request goes through the following states: + + ALLOCATED (user-thread) + + The request has been allocated in sfetch_send() and is + waiting to be dispatched into its IO channel. When this + happens, the request will transition into the DISPATCHED state. + + DISPATCHED (IO thread) + + The request has been dispatched into its IO channel, and a + lane has been assigned to the request. + + If a buffer was provided in sfetch_send() the request will + immediately transition into the FETCHING state and start loading + data into the buffer. + + If no buffer was provided in sfetch_send(), the response + callback will be called with (response->dispatched == true), + so that the response callback can bind a buffer to the + request. Binding the buffer in the response callback makes + sense if the buffer isn't dynamically allocated, but instead + a pre-allocated buffer must be selected from the request's + channel and lane. + + Note that it isn't possible to get a file size in the response callback + which would help with allocating a buffer of the right size, this is + because it isn't possible in HTTP to query the file size before the + entire file is downloaded (...when the web server serves files compressed). + + If opening the file failed, the request will transition into + the FAILED state with the error code SFETCH_ERROR_FILE_NOT_FOUND. + + FETCHING (IO thread) + + While a request is in the FETCHING state, data will be loaded into + the user-provided buffer. + + If no buffer was provided, the request will go into the FAILED + state with the error code SFETCH_ERROR_NO_BUFFER. + + If a buffer was provided, but it is too small to contain the + fetched data, the request will go into the FAILED state with + error code SFETCH_ERROR_BUFFER_TOO_SMALL. + + If less data can be read from the file than expected, the request + will go into the FAILED state with error code SFETCH_ERROR_UNEXPECTED_EOF. + + If loading data into the provided buffer works as expected, the + request will go into the FETCHED state. + + FETCHED (user thread) + + The request goes into the FETCHED state either when the entire file + has been loaded into the provided buffer (when request.chunk_size == 0), + or a chunk has been loaded (and optionally decompressed) into the + buffer (when request.chunk_size > 0). + + The response callback will be called so that the user-code can + process the loaded data using the following sfetch_response_t struct members: + + - data.ptr: pointer to the start of fetched data + - data.size: the number of bytes in the provided buffer + - data_offset: the byte offset of the loaded data chunk in the + overall file (this is only set to a non-zero value in a streaming + scenario) + + Once all file data has been loaded, the 'finished' flag will be set + in the response callback's sfetch_response_t argument. + + After the user callback returns, and all file data has been loaded + (response.finished flag is set) the request has reached its end-of-life + and will be recycled. + + Otherwise, if there's still data to load (because streaming was + requested by providing a non-zero request.chunk_size), the request + will switch back to the FETCHING state to load the next chunk of data. + + Note that it is ok to associate a different buffer or buffer-size + with the request by calling sfetch_bind_buffer() in the response-callback. + + To check in the response callback for the FETCHED state, and + independently whether the request is finished: + + void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // request is in FETCHED state, the loaded data is available + // in .data.ptr, and the number of bytes that have been + // loaded in .data.size: + const void* data = response->data.ptr; + size_t num_bytes = response->data.size; + } + if (response->finished) { + // the finished flag is set either when all data + // has been loaded, the request has been cancelled, + // or the file operation has failed, this is where + // any required per-request cleanup work should happen + } + } + + + FAILED (user thread) + + A request will transition into the FAILED state in the following situations: + + - if the file doesn't exist or couldn't be opened for other + reasons (SFETCH_ERROR_FILE_NOT_FOUND) + - if no buffer is associated with the request in the FETCHING state + (SFETCH_ERROR_NO_BUFFER) + - if the provided buffer is too small to hold the entire file + (if request.chunk_size == 0), or the (potentially decompressed) + partial data chunk (SFETCH_ERROR_BUFFER_TOO_SMALL) + - if less bytes could be read from the file then expected + (SFETCH_ERROR_UNEXPECTED_EOF) + - if a request has been cancelled via sfetch_cancel() + (SFETCH_ERROR_CANCELLED) + + The response callback will be called once after a request goes into + the FAILED state, with the 'response->finished' and + 'response->failed' flags set to true. + + This gives the user-code a chance to cleanup any resources associated + with the request. + + To check for the failed state in the response callback: + + void response_callback(const sfetch_response_t* response) { + if (response->failed) { + // specifically check for the failed state... + } + // or you can do a catch-all check via the finished-flag: + if (response->finished) { + if (response->failed) { + // if more detailed error handling is needed: + switch (response->error_code) { + ... + } + } + } + } + + PAUSED (user thread) + + A request will transition into the PAUSED state after user-code + calls the function sfetch_pause() on the request's handle. Usually + this happens from within the response-callback in streaming scenarios + when the data streaming needs to wait for a data decoder (like + a video/audio player) to catch up. + + While a request is in PAUSED state, the response-callback will be + called in each sfetch_dowork(), so that the user-code can either + continue the request by calling sfetch_continue(), or cancel + the request by calling sfetch_cancel(). + + When calling sfetch_continue() on a paused request, the request will + transition into the FETCHING state. Otherwise if sfetch_cancel() is + called, the request will switch into the FAILED state. + + To check for the PAUSED state in the response callback: + + void response_callback(const sfetch_response_t* response) { + if (response->paused) { + // we can check here whether the request should + // continue to load data: + if (should_continue(response->handle)) { + sfetch_continue(response->handle); + } + } + } + + + CHUNK SIZE AND HTTP COMPRESSION + =============================== + TL;DR: for streaming scenarios, the provided chunk-size must be smaller + than the provided buffer-size because the web server may decide to + serve the data compressed and the chunk-size must be given in 'compressed + bytes' while the buffer receives 'uncompressed bytes'. It's not possible + in HTTP to query the uncompressed size for a compressed download until + that download has finished. + + With vanilla HTTP, it is not possible to query the actual size of a file + without downloading the entire file first (the Content-Length response + header only provides the compressed size). Furthermore, for HTTP + range-requests, the range is given on the compressed data, not the + uncompressed data. So if the web server decides to serve the data + compressed, the content-length and range-request parameters don't + correspond to the uncompressed data that's arriving in the sokol-fetch + buffers, and there's no way from JS or WASM to either force uncompressed + downloads (e.g. by setting the Accept-Encoding field), or access the + compressed data. + + This has some implications for sokol_fetch.h, most notably that buffers + can't be provided in the exactly right size, because that size can't + be queried from HTTP before the data is actually downloaded. + + When downloading whole files at once, it is basically expected that you + know the maximum files size upfront through other means (for instance + through a separate meta-data-file which contains the file sizes and + other meta-data for each file that needs to be loaded). + + For streaming downloads the situation is a bit more complicated. These + use HTTP range-requests, and those ranges are defined on the (potentially) + compressed data which the JS/WASM side doesn't have access to. However, + the JS/WASM side only ever sees the uncompressed data, and it's not possible + to query the uncompressed size of a range request before that range request + has finished. + + If the provided buffer is too small to contain the uncompressed data, + the request will fail with error code SFETCH_ERROR_BUFFER_TOO_SMALL. + + + CHANNELS AND LANES + ================== + Channels and lanes are (somewhat artificial) concepts to manage + parallelization, prioritization and rate-limiting. + + Channels can be used to parallelize message processing for better 'pipeline + throughput', and to prioritize requests: user-code could reserve one + channel for streaming downloads which need to run in parallel to other + requests, another channel for "regular" downloads and yet another + high-priority channel which would only be used for small files which need + to start loading immediately. + + Each channel comes with its own IO thread and message queues for pumping + messages in and out of the thread. The channel where a request is + processed is selected manually when sending a message: + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = my_response_callback, + .channel = 2 + }); + + The number of channels is configured at startup in sfetch_setup() and + cannot be changed afterwards. + + Channels are completely separate from each other, and a request will + never "hop" from one channel to another. + + Each channel consists of a fixed number of "lanes" for automatic rate + limiting: + + When a request is sent to a channel via sfetch_send(), a "free lane" will + be picked and assigned to the request. The request will occupy this lane + for its entire life time (also while it is paused). If all lanes of a + channel are currently occupied, new requests will wait until a + lane becomes unoccupied. + + Since the number of channels and lanes is known upfront, it is guaranteed + that there will never be more than "num_channels * num_lanes" requests + in flight at any one time. + + This guarantee eliminates unexpected load- and memory-spikes when + many requests are sent in very short time, and it allows to pre-allocate + a fixed number of memory buffers which can be reused for the entire + "lifetime" of a sokol-fetch context. + + In the most simple scenario - when a maximum file size is known - buffers + can be statically allocated like this: + + uint8_t buffer[NUM_CHANNELS][NUM_LANES][MAX_FILE_SIZE]; + + Then in the user callback pick a buffer by channel and lane, + and associate it with the request like this: + + void response_callback(const sfetch_response_t* response) { + if (response->dispatched) { + void* ptr = buffer[response->channel][response->lane]; + sfetch_bind_buffer(response->handle, ptr, MAX_FILE_SIZE); + } + ... + } + + + NOTES ON OPTIMIZING PIPELINE LATENCY AND THROUGHPUT + =================================================== + With the default configuration of 1 channel and 1 lane per channel, + sokol_fetch.h will appear to have a shockingly bad loading performance + if several files are loaded. + + This has two reasons: + + (1) all parallelization when loading data has been disabled. A new + request will only be processed, when the last request has finished. + + (2) every invocation of the response-callback adds one frame of latency + to the request, because callbacks will only be called from within + sfetch_dowork() + + sokol-fetch takes a few shortcuts to improve step (2) and reduce + the 'inherent latency' of a request: + + - if a buffer is provided upfront, the response-callback won't be + called in the DISPATCHED state, but start right with the FETCHED state + where data has already been loaded into the buffer + + - there is no separate CLOSED state where the callback is invoked + separately when loading has finished (or the request has failed), + instead the finished and failed flags will be set as part of + the last FETCHED invocation + + This means providing a big-enough buffer to fit the entire file is the + best case, the response callback will only be called once, ideally in + the next frame (or two calls to sfetch_dowork()). + + If no buffer is provided upfront, one frame of latency is added because + the response callback needs to be invoked in the DISPATCHED state so that + the user code can bind a buffer. + + This means the best case for a request without an upfront-provided + buffer is 2 frames (or 3 calls to sfetch_dowork()). + + That's about what can be done to improve the latency for a single request, + but the really important step is to improve overall throughput. If you + need to load thousands of files you don't want that to be completely + serialized. + + The most important action to increase throughput is to increase the + number of lanes per channel. This defines how many requests can be + 'in flight' on a single channel at the same time. The guiding decision + factor for how many lanes you can "afford" is the memory size you want + to set aside for buffers. Each lane needs its own buffer so that + the data loaded for one request doesn't scribble over the data + loaded for another request. + + Here's a simple example of sending 4 requests without upfront buffer + on a channel with 1, 2 and 4 lanes, each line is one frame: + + 1 LANE (8 frames): + Lane 0: + ------------- + REQ 0 DISPATCHED + REQ 0 FETCHED + REQ 1 DISPATCHED + REQ 1 FETCHED + REQ 2 DISPATCHED + REQ 2 FETCHED + REQ 3 DISPATCHED + REQ 3 FETCHED + + Note how the request don't overlap, so they can all use the same buffer. + + 2 LANES (4 frames): + Lane 0: Lane 1: + ------------------------------------ + REQ 0 DISPATCHED REQ 1 DISPATCHED + REQ 0 FETCHED REQ 1 FETCHED + REQ 2 DISPATCHED REQ 3 DISPATCHED + REQ 2 FETCHED REQ 3 FETCHED + + This reduces the overall time to 4 frames, but now you need 2 buffers so + that requests don't scribble over each other. + + 4 LANES (2 frames): + Lane 0: Lane 1: Lane 2: Lane 3: + ---------------------------------------------------------------------------- + REQ 0 DISPATCHED REQ 1 DISPATCHED REQ 2 DISPATCHED REQ 3 DISPATCHED + REQ 0 FETCHED REQ 1 FETCHED REQ 2 FETCHED REQ 3 FETCHED + + Now we're down to the same 'best-case' latency as sending a single + request. + + Apart from the memory requirements for the streaming buffers (which is + under your control), you can be generous with the number of lanes, + they don't add any processing overhead. + + The last option for tweaking latency and throughput is channels. Each + channel works independently from other channels, so while one + channel is busy working through a large number of requests (or one + very long streaming download), you can set aside a high-priority channel + for requests that need to start as soon as possible. + + On platforms with threading support, each channel runs on its own + thread, but this is mainly an implementation detail to work around + the traditional blocking file IO functions, not for performance reasons. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sfetch_setup(&(sfetch_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_fetch.h + itself though, not any allocations in OS libraries. + + Memory allocation will only happen on the same thread where sfetch_setup() + was called, so you don't need to worry about thread-safety. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sfetch_setup(&(sfetch_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sfetch' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SFETCH_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_fetch.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-fetch like this: + + sfetch_setup(&(sfetch_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + FUTURE PLANS / V2.0 IDEA DUMP + ============================= + - An optional polling API (as alternative to callback API) + - Move buffer-management into the API? The "manual management" + can be quite tricky especially for dynamic allocation scenarios, + API support for buffer management would simplify cases like + preventing that requests scribble over each other's buffers, or + an automatic garbage collection for dynamically allocated buffers, + or automatically falling back to dynamic allocation if static + buffers aren't big enough. + - Pluggable request handlers to load data from other "sources" + (especially HTTP downloads on native platforms via e.g. libcurl + would be useful) + - I'm currently not happy how the user-data block is handled, this + should getting and updating the user-data should be wrapped by + API functions (similar to bind/unbind buffer) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2019 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_FETCH_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_FETCH_API_DECL) +#define SOKOL_FETCH_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_FETCH_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FETCH_IMPL) +#define SOKOL_FETCH_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_FETCH_API_DECL __declspec(dllimport) +#else +#define SOKOL_FETCH_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sfetch_log_item_t + + Log items are defined via X-Macros, and expanded to an + enum 'sfetch_log_item', and in debug mode only, + corresponding strings. + + Used as parameter in the logging callback. +*/ +#define _SFETCH_LOG_ITEMS \ + _SFETCH_LOGITEM_XMACRO(OK, "Ok") \ + _SFETCH_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SFETCH_LOGITEM_XMACRO(FILE_PATH_UTF8_DECODING_FAILED, "failed converting file path from UTF8 to wide") \ + _SFETCH_LOGITEM_XMACRO(SEND_QUEUE_FULL, "send queue full (adjust via sfetch_desc_t.max_requests)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_CHANNEL_INDEX_TOO_BIG, "channel index too big (adjust via sfetch_desc_t.num_channels)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_PATH_IS_NULL, "file path is nullptr (sfetch_request_t.path)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_PATH_TOO_LONG, "file path is too long (SFETCH_MAX_PATH)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_CALLBACK_MISSING, "no callback provided (sfetch_request_t.callback)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE, "chunk size is greater buffer size (sfetch_request_t.chunk_size vs .buffer.size)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL, "user data ptr is set but user data size is null (sfetch_request_t.user_data.ptr vs .size)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT, "user data ptr is null but size is not (sfetch_request_t.user_data.ptr vs .size)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_USERDATA_SIZE_TOO_BIG, "user data size too big (see SFETCH_MAX_USERDATA_UINT64)") \ + _SFETCH_LOGITEM_XMACRO(CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS, "clamping num channels to SFETCH_MAX_CHANNELS") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_POOL_EXHAUSTED, "request pool exhausted (tweak via sfetch_desc_t.max_requests)") \ + +#define _SFETCH_LOGITEM_XMACRO(item,msg) SFETCH_LOGITEM_##item, +typedef enum sfetch_log_item_t { + _SFETCH_LOG_ITEMS +} sfetch_log_item_t; +#undef _SFETCH_LOGITEM_XMACRO + +/* + sfetch_logger_t + + Used in sfetch_desc_t to provide a custom logging and error reporting + callback to sokol-fetch. +*/ +typedef struct sfetch_logger_t { + void (*func)( + const char* tag, // always "sfetch" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SFETCH_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_fetch.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sfetch_logger_t; + +/* + sfetch_range_t + + A pointer-size pair struct to pass memory ranges into and out of sokol-fetch. + When initialized from a value type (array or struct) you can use the + SFETCH_RANGE() helper macro to build an sfetch_range_t struct. +*/ +typedef struct sfetch_range_t { + const void* ptr; + size_t size; +} sfetch_range_t; + +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) // /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' +#pragma warning(disable:4204) // VS2015: nonstandard extension used: non-constant aggregate initializer +#endif +#if defined(__cplusplus) +#define SFETCH_RANGE(x) sfetch_range_t{ &x, sizeof(x) } +#else +#define SFETCH_RANGE(x) (sfetch_range_t){ &x, sizeof(x) } +#endif + +/* + sfetch_allocator_t + + Used in sfetch_desc_t to provide custom memory-alloc and -free functions + to sokol_fetch.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sfetch_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sfetch_allocator_t; + +/* configuration values for sfetch_setup() */ +typedef struct sfetch_desc_t { + uint32_t max_requests; // max number of active requests across all channels (default: 128) + uint32_t num_channels; // number of channels to fetch requests in parallel (default: 1) + uint32_t num_lanes; // max number of requests active on the same channel (default: 1) + sfetch_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + sfetch_logger_t logger; // optional log function overrides (default: NO LOGGING!) +} sfetch_desc_t; + +/* a request handle to identify an active fetch request, returned by sfetch_send() */ +typedef struct sfetch_handle_t { uint32_t id; } sfetch_handle_t; + +/* error codes */ +typedef enum sfetch_error_t { + SFETCH_ERROR_NO_ERROR, + SFETCH_ERROR_FILE_NOT_FOUND, + SFETCH_ERROR_NO_BUFFER, + SFETCH_ERROR_BUFFER_TOO_SMALL, + SFETCH_ERROR_UNEXPECTED_EOF, + SFETCH_ERROR_INVALID_HTTP_STATUS, + SFETCH_ERROR_CANCELLED, + SFETCH_ERROR_JS_OTHER, // check browser console for detailed error info +} sfetch_error_t; + +/* the response struct passed to the response callback */ +typedef struct sfetch_response_t { + sfetch_handle_t handle; // request handle this response belongs to + bool dispatched; // true when request is in DISPATCHED state (lane has been assigned) + bool fetched; // true when request is in FETCHED state (fetched data is available) + bool paused; // request is currently in paused state + bool finished; // this is the last response for this request + bool failed; // request has failed (always set together with 'finished') + bool cancelled; // request was cancelled (always set together with 'finished') + sfetch_error_t error_code; // more detailed error code when failed is true + uint32_t channel; // the channel which processes this request + uint32_t lane; // the lane this request occupies on its channel + const char* path; // the original filesystem path of the request + void* user_data; // pointer to read/write user-data area + uint32_t data_offset; // current offset of fetched data chunk in the overall file data + sfetch_range_t data; // the fetched data as ptr/size pair (data.ptr == buffer.ptr, data.size <= buffer.size) + sfetch_range_t buffer; // the user-provided buffer which holds the fetched data +} sfetch_response_t; + +/* request parameters passed to sfetch_send() */ +typedef struct sfetch_request_t { + uint32_t channel; // index of channel this request is assigned to (default: 0) + const char* path; // filesystem path or HTTP URL (required) + void (*callback) (const sfetch_response_t*); // response callback function pointer (required) + uint32_t chunk_size; // number of bytes to load per stream-block (optional) + sfetch_range_t buffer; // a memory buffer where the data will be loaded into (optional) + sfetch_range_t user_data; // ptr/size of a POD user data block which will be memcpy'd (optional) +} sfetch_request_t; + +/* setup sokol-fetch (can be called on multiple threads) */ +SOKOL_FETCH_API_DECL void sfetch_setup(const sfetch_desc_t* desc); +/* discard a sokol-fetch context */ +SOKOL_FETCH_API_DECL void sfetch_shutdown(void); +/* return true if sokol-fetch has been setup */ +SOKOL_FETCH_API_DECL bool sfetch_valid(void); +/* get the desc struct that was passed to sfetch_setup() */ +SOKOL_FETCH_API_DECL sfetch_desc_t sfetch_desc(void); +/* return the max userdata size in number of bytes (SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) */ +SOKOL_FETCH_API_DECL int sfetch_max_userdata_bytes(void); +/* return the value of the SFETCH_MAX_PATH implementation config value */ +SOKOL_FETCH_API_DECL int sfetch_max_path(void); + +/* send a fetch-request, get handle to request back */ +SOKOL_FETCH_API_DECL sfetch_handle_t sfetch_send(const sfetch_request_t* request); +/* return true if a handle is valid *and* the request is alive */ +SOKOL_FETCH_API_DECL bool sfetch_handle_valid(sfetch_handle_t h); +/* do per-frame work, moves requests into and out of IO threads, and invokes response-callbacks */ +SOKOL_FETCH_API_DECL void sfetch_dowork(void); + +/* bind a data buffer to a request (request must not currently have a buffer bound, must be called from response callback */ +SOKOL_FETCH_API_DECL void sfetch_bind_buffer(sfetch_handle_t h, sfetch_range_t buffer); +/* clear the 'buffer binding' of a request, returns previous buffer pointer (can be 0), must be called from response callback */ +SOKOL_FETCH_API_DECL void* sfetch_unbind_buffer(sfetch_handle_t h); +/* cancel a request that's in flight (will call response callback with .cancelled + .finished) */ +SOKOL_FETCH_API_DECL void sfetch_cancel(sfetch_handle_t h); +/* pause a request (will call response callback each frame with .paused) */ +SOKOL_FETCH_API_DECL void sfetch_pause(sfetch_handle_t h); +/* continue a paused request */ +SOKOL_FETCH_API_DECL void sfetch_continue(sfetch_handle_t h); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for c++ */ +inline void sfetch_setup(const sfetch_desc_t& desc) { return sfetch_setup(&desc); } +inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfetch_send(&request); } + +#endif +#endif // SOKOL_FETCH_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_FETCH_IMPL +#define SOKOL_FETCH_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sfetch_desc_t.allocator to override memory allocation functions" +#endif + +#include /* malloc, free */ +#include /* memset, memcpy */ + +#ifndef SFETCH_MAX_PATH +#define SFETCH_MAX_PATH (1024) +#endif +#ifndef SFETCH_MAX_USERDATA_UINT64 +#define SFETCH_MAX_USERDATA_UINT64 (16) +#endif +#ifndef SFETCH_MAX_CHANNELS +#define SFETCH_MAX_CHANNELS (16) +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(__EMSCRIPTEN__) + #include + #define _SFETCH_PLATFORM_EMSCRIPTEN (1) + #define _SFETCH_PLATFORM_WINDOWS (0) + #define _SFETCH_PLATFORM_POSIX (0) + #define _SFETCH_HAS_THREADS (0) +#elif defined(_WIN32) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #define _SFETCH_PLATFORM_WINDOWS (1) + #define _SFETCH_PLATFORM_EMSCRIPTEN (0) + #define _SFETCH_PLATFORM_POSIX (0) + #define _SFETCH_HAS_THREADS (1) +#else + #include + #include /* fopen, fread, fseek, fclose */ + #define _SFETCH_PLATFORM_POSIX (1) + #define _SFETCH_PLATFORM_EMSCRIPTEN (0) + #define _SFETCH_PLATFORM_WINDOWS (0) + #define _SFETCH_HAS_THREADS (1) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4724) // potential mod by 0 +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs +typedef struct _sfetch_path_t { + char buf[SFETCH_MAX_PATH]; +} _sfetch_path_t; + +/* a thread with incoming and outgoing message queue syncing */ +#if _SFETCH_PLATFORM_POSIX +typedef struct { + pthread_t thread; + pthread_cond_t incoming_cond; + pthread_mutex_t incoming_mutex; + pthread_mutex_t outgoing_mutex; + pthread_mutex_t running_mutex; + pthread_mutex_t stop_mutex; + bool stop_requested; + bool valid; +} _sfetch_thread_t; +#elif _SFETCH_PLATFORM_WINDOWS +typedef struct { + HANDLE thread; + HANDLE incoming_event; + CRITICAL_SECTION incoming_critsec; + CRITICAL_SECTION outgoing_critsec; + CRITICAL_SECTION running_critsec; + CRITICAL_SECTION stop_critsec; + bool stop_requested; + bool valid; +} _sfetch_thread_t; +#endif + +/* file handle abstraction */ +#if _SFETCH_PLATFORM_POSIX +typedef FILE* _sfetch_file_handle_t; +#define _SFETCH_INVALID_FILE_HANDLE (0) +typedef void*(*_sfetch_thread_func_t)(void*); +#elif _SFETCH_PLATFORM_WINDOWS +typedef HANDLE _sfetch_file_handle_t; +#define _SFETCH_INVALID_FILE_HANDLE (INVALID_HANDLE_VALUE) +typedef LPTHREAD_START_ROUTINE _sfetch_thread_func_t; +#endif + +/* user-side per-request state */ +typedef struct { + bool pause; /* switch item to PAUSED state if true */ + bool cont; /* switch item back to FETCHING if true */ + bool cancel; /* cancel the request, switch into FAILED state */ + /* transfer IO => user thread */ + uint32_t fetched_offset; /* number of bytes fetched so far */ + uint32_t fetched_size; /* size of last fetched chunk */ + sfetch_error_t error_code; + bool finished; + /* user thread only */ + size_t user_data_size; + uint64_t user_data[SFETCH_MAX_USERDATA_UINT64]; +} _sfetch_item_user_t; + +/* thread-side per-request state */ +typedef struct { + /* transfer IO => user thread */ + uint32_t fetched_offset; + uint32_t fetched_size; + sfetch_error_t error_code; + bool failed; + bool finished; + /* IO thread only */ + #if _SFETCH_PLATFORM_EMSCRIPTEN + uint32_t http_range_offset; + #else + _sfetch_file_handle_t file_handle; + #endif + uint32_t content_size; +} _sfetch_item_thread_t; + +/* a request goes through the following states, ping-ponging between IO and user thread */ +typedef enum _sfetch_state_t { + _SFETCH_STATE_INITIAL, /* internal: request has just been initialized */ + _SFETCH_STATE_ALLOCATED, /* internal: request has been allocated from internal pool */ + _SFETCH_STATE_DISPATCHED, /* user thread: request has been dispatched to its IO channel */ + _SFETCH_STATE_FETCHING, /* IO thread: waiting for data to be fetched */ + _SFETCH_STATE_FETCHED, /* user thread: fetched data available */ + _SFETCH_STATE_PAUSED, /* user thread: request has been paused via sfetch_pause() */ + _SFETCH_STATE_FAILED, /* user thread: follow state or FETCHING if something went wrong */ +} _sfetch_state_t; + +/* an internal request item */ +#define _SFETCH_INVALID_LANE (0xFFFFFFFF) +typedef struct { + sfetch_handle_t handle; + _sfetch_state_t state; + uint32_t channel; + uint32_t lane; + uint32_t chunk_size; + void (*callback) (const sfetch_response_t*); + sfetch_range_t buffer; + + /* updated by IO-thread, off-limits to user thread */ + _sfetch_item_thread_t thread; + + /* accessible by user-thread, off-limits to IO thread */ + _sfetch_item_user_t user; + + /* big stuff at the end */ + _sfetch_path_t path; +} _sfetch_item_t; + +/* a pool of internal per-request items */ +typedef struct { + uint32_t size; + uint32_t free_top; + _sfetch_item_t* items; + uint32_t* free_slots; + uint32_t* gen_ctrs; + bool valid; +} _sfetch_pool_t; + +/* a ringbuffer for pool-slot ids */ +typedef struct { + uint32_t head; + uint32_t tail; + uint32_t num; + uint32_t* buf; +} _sfetch_ring_t; + +/* an IO channel with its own IO thread */ +struct _sfetch_t; +typedef struct { + struct _sfetch_t* ctx; // back-pointer to thread-local _sfetch state pointer, since this isn't accessible from the IO threads + _sfetch_ring_t free_lanes; + _sfetch_ring_t user_sent; + _sfetch_ring_t user_incoming; + _sfetch_ring_t user_outgoing; + #if _SFETCH_HAS_THREADS + _sfetch_ring_t thread_incoming; + _sfetch_ring_t thread_outgoing; + _sfetch_thread_t thread; + #endif + void (*request_handler)(struct _sfetch_t* ctx, uint32_t slot_id); + bool valid; +} _sfetch_channel_t; + +/* the sfetch global state */ +typedef struct _sfetch_t { + bool setup; + bool valid; + bool in_callback; + sfetch_desc_t desc; + _sfetch_pool_t pool; + _sfetch_channel_t chn[SFETCH_MAX_CHANNELS]; +} _sfetch_t; +#if _SFETCH_HAS_THREADS +#if defined(_MSC_VER) +static __declspec(thread) _sfetch_t* _sfetch; +#else +static __thread _sfetch_t* _sfetch; +#endif +#else +static _sfetch_t* _sfetch; +#endif +#define _sfetch_def(val, def) (((val) == 0) ? (def) : (val)) + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SFETCH_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sfetch_log_messages[] = { + _SFETCH_LOG_ITEMS +}; +#undef _SFETCH_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SFETCH_PANIC(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 0, __LINE__) +#define _SFETCH_ERROR(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 1, __LINE__) +#define _SFETCH_WARN(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 2, __LINE__) +#define _SFETCH_INFO(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 3, __LINE__) + +static void _sfetch_log(sfetch_log_item_t log_item, uint32_t log_level, uint32_t line_nr) { + if (_sfetch->desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _sfetch_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _sfetch->desc.logger.func("sfetch", log_level, (uint32_t)log_item, message, line_nr, filename, _sfetch->desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _sfetch_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sfetch_malloc_with_allocator(const sfetch_allocator_t* allocator, size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (allocator->alloc_fn) { + ptr = allocator->alloc_fn(size, allocator->user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SFETCH_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sfetch_malloc(size_t size) { + return _sfetch_malloc_with_allocator(&_sfetch->desc.allocator, size); +} + +_SOKOL_PRIVATE void* _sfetch_malloc_clear(size_t size) { + void* ptr = _sfetch_malloc(size); + _sfetch_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sfetch_free(void* ptr) { + if (_sfetch->desc.allocator.free_fn) { + _sfetch->desc.allocator.free_fn(ptr, _sfetch->desc.allocator.user_data); + } else { + free(ptr); + } +} + +_SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) { + return _sfetch; +} + +_SOKOL_PRIVATE void _sfetch_path_copy(_sfetch_path_t* dst, const char* src) { + SOKOL_ASSERT(dst); + if (src && (strlen(src) < SFETCH_MAX_PATH)) { + #if defined(_MSC_VER) + strncpy_s(dst->buf, SFETCH_MAX_PATH, src, (SFETCH_MAX_PATH-1)); + #else + strncpy(dst->buf, src, SFETCH_MAX_PATH); + #endif + dst->buf[SFETCH_MAX_PATH-1] = 0; + } else { + _sfetch_clear(dst->buf, SFETCH_MAX_PATH); + } +} + +_SOKOL_PRIVATE _sfetch_path_t _sfetch_path_make(const char* str) { + _sfetch_path_t res; + _sfetch_path_copy(&res, str); + return res; +} + +// ███ ███ ███████ ███████ ███████ █████ ██████ ███████ ██████ ██ ██ ███████ ██ ██ ███████ +// ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ███████ ███████ ███████ ██ ███ █████ ██ ██ ██ ██ █████ ██ ██ █████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ███████ ██ ██ ██████ ███████ ██████ ██████ ███████ ██████ ███████ +// ▀▀ +// >>message queue +_SOKOL_PRIVATE uint32_t _sfetch_ring_wrap(const _sfetch_ring_t* rb, uint32_t i) { + return i % rb->num; +} + +_SOKOL_PRIVATE void _sfetch_ring_discard(_sfetch_ring_t* rb) { + SOKOL_ASSERT(rb); + if (rb->buf) { + _sfetch_free(rb->buf); + rb->buf = 0; + } + rb->head = 0; + rb->tail = 0; + rb->num = 0; +} + +_SOKOL_PRIVATE bool _sfetch_ring_init(_sfetch_ring_t* rb, uint32_t num_slots) { + SOKOL_ASSERT(rb && (num_slots > 0)); + SOKOL_ASSERT(0 == rb->buf); + rb->head = 0; + rb->tail = 0; + /* one slot reserved to detect full vs empty */ + rb->num = num_slots + 1; + const size_t queue_size = rb->num * sizeof(sfetch_handle_t); + rb->buf = (uint32_t*) _sfetch_malloc_clear(queue_size); + if (rb->buf) { + return true; + } else { + _sfetch_ring_discard(rb); + return false; + } +} + +_SOKOL_PRIVATE bool _sfetch_ring_full(const _sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + return _sfetch_ring_wrap(rb, rb->head + 1) == rb->tail; +} + +_SOKOL_PRIVATE bool _sfetch_ring_empty(const _sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + return rb->head == rb->tail; +} + +_SOKOL_PRIVATE uint32_t _sfetch_ring_count(const _sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + uint32_t count; + if (rb->head >= rb->tail) { + count = rb->head - rb->tail; + } else { + count = (rb->head + rb->num) - rb->tail; + } + SOKOL_ASSERT(count < rb->num); + return count; +} + +_SOKOL_PRIVATE void _sfetch_ring_enqueue(_sfetch_ring_t* rb, uint32_t slot_id) { + SOKOL_ASSERT(rb && rb->buf); + SOKOL_ASSERT(!_sfetch_ring_full(rb)); + SOKOL_ASSERT(rb->head < rb->num); + rb->buf[rb->head] = slot_id; + rb->head = _sfetch_ring_wrap(rb, rb->head + 1); +} + +_SOKOL_PRIVATE uint32_t _sfetch_ring_dequeue(_sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + SOKOL_ASSERT(!_sfetch_ring_empty(rb)); + SOKOL_ASSERT(rb->tail < rb->num); + uint32_t slot_id = rb->buf[rb->tail]; + rb->tail = _sfetch_ring_wrap(rb, rb->tail + 1); + return slot_id; +} + +_SOKOL_PRIVATE uint32_t _sfetch_ring_peek(const _sfetch_ring_t* rb, uint32_t index) { + SOKOL_ASSERT(rb && rb->buf); + SOKOL_ASSERT(!_sfetch_ring_empty(rb)); + SOKOL_ASSERT(index < _sfetch_ring_count(rb)); + uint32_t rb_index = _sfetch_ring_wrap(rb, rb->tail + index); + return rb->buf[rb_index]; +} + +// ██████ ███████ ██████ ██ ██ ███████ ███████ ████████ ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ █████ ██ ██ ██ ██ █████ ███████ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██████ ██████ ███████ ███████ ██ ██ ██████ ██████ ███████ +// ▀▀ +// >>request pool +_SOKOL_PRIVATE uint32_t _sfetch_make_id(uint32_t index, uint32_t gen_ctr) { + return (gen_ctr<<16) | (index & 0xFFFF); +} + +_SOKOL_PRIVATE sfetch_handle_t _sfetch_make_handle(uint32_t slot_id) { + sfetch_handle_t h; + h.id = slot_id; + return h; +} + +_SOKOL_PRIVATE uint32_t _sfetch_slot_index(uint32_t slot_id) { + return slot_id & 0xFFFF; +} + +_SOKOL_PRIVATE void _sfetch_item_init(_sfetch_item_t* item, uint32_t slot_id, const sfetch_request_t* request) { + SOKOL_ASSERT(item && (0 == item->handle.id)); + SOKOL_ASSERT(request && request->path); + _sfetch_clear(item, sizeof(_sfetch_item_t)); + item->handle.id = slot_id; + item->state = _SFETCH_STATE_INITIAL; + item->channel = request->channel; + item->chunk_size = request->chunk_size; + item->lane = _SFETCH_INVALID_LANE; + item->callback = request->callback; + item->buffer = request->buffer; + item->path = _sfetch_path_make(request->path); + #if !_SFETCH_PLATFORM_EMSCRIPTEN + item->thread.file_handle = _SFETCH_INVALID_FILE_HANDLE; + #endif + if (request->user_data.ptr && + (request->user_data.size > 0) && + (request->user_data.size <= (SFETCH_MAX_USERDATA_UINT64*8))) + { + item->user.user_data_size = request->user_data.size; + memcpy(item->user.user_data, request->user_data.ptr, request->user_data.size); + } +} + +_SOKOL_PRIVATE void _sfetch_item_discard(_sfetch_item_t* item) { + SOKOL_ASSERT(item && (0 != item->handle.id)); + _sfetch_clear(item, sizeof(_sfetch_item_t)); +} + +_SOKOL_PRIVATE void _sfetch_pool_discard(_sfetch_pool_t* pool) { + SOKOL_ASSERT(pool); + if (pool->free_slots) { + _sfetch_free(pool->free_slots); + pool->free_slots = 0; + } + if (pool->gen_ctrs) { + _sfetch_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + } + if (pool->items) { + _sfetch_free(pool->items); + pool->items = 0; + } + pool->size = 0; + pool->free_top = 0; + pool->valid = false; +} + +_SOKOL_PRIVATE bool _sfetch_pool_init(_sfetch_pool_t* pool, uint32_t num_items) { + SOKOL_ASSERT(pool && (num_items > 0) && (num_items < ((1<<16)-1))); + SOKOL_ASSERT(0 == pool->items); + /* NOTE: item slot 0 is reserved for the special "invalid" item index 0*/ + pool->size = num_items + 1; + pool->free_top = 0; + const size_t items_size = pool->size * sizeof(_sfetch_item_t); + pool->items = (_sfetch_item_t*) _sfetch_malloc_clear(items_size); + /* generation counters indexable by pool slot index, slot 0 is reserved */ + const size_t gen_ctrs_size = sizeof(uint32_t) * pool->size; + pool->gen_ctrs = (uint32_t*) _sfetch_malloc_clear(gen_ctrs_size); + SOKOL_ASSERT(pool->gen_ctrs); + /* NOTE: it's not a bug to only reserve num_items here */ + const size_t free_slots_size = num_items * sizeof(int); + pool->free_slots = (uint32_t*) _sfetch_malloc_clear(free_slots_size); + if (pool->items && pool->free_slots) { + /* never allocate the 0-th item, this is the reserved 'invalid item' */ + for (uint32_t i = pool->size - 1; i >= 1; i--) { + pool->free_slots[pool->free_top++] = i; + } + pool->valid = true; + } else { + /* allocation error */ + _sfetch_pool_discard(pool); + } + return pool->valid; +} + +_SOKOL_PRIVATE uint32_t _sfetch_pool_item_alloc(_sfetch_pool_t* pool, const sfetch_request_t* request) { + SOKOL_ASSERT(pool && pool->valid); + if (pool->free_top > 0) { + uint32_t slot_index = pool->free_slots[--pool->free_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + uint32_t slot_id = _sfetch_make_id(slot_index, ++pool->gen_ctrs[slot_index]); + _sfetch_item_init(&pool->items[slot_index], slot_id, request); + pool->items[slot_index].state = _SFETCH_STATE_ALLOCATED; + return slot_id; + } else { + /* pool exhausted, return the 'invalid handle' */ + return _sfetch_make_id(0, 0); + } +} + +_SOKOL_PRIVATE void _sfetch_pool_item_free(_sfetch_pool_t* pool, uint32_t slot_id) { + SOKOL_ASSERT(pool && pool->valid); + uint32_t slot_index = _sfetch_slot_index(slot_id); + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + SOKOL_ASSERT(pool->items[slot_index].handle.id == slot_id); + #if defined(SOKOL_DEBUG) + /* debug check against double-free */ + for (uint32_t i = 0; i < pool->free_top; i++) { + SOKOL_ASSERT(pool->free_slots[i] != slot_index); + } + #endif + _sfetch_item_discard(&pool->items[slot_index]); + pool->free_slots[pool->free_top++] = slot_index; + SOKOL_ASSERT(pool->free_top <= (pool->size - 1)); +} + +/* return pointer to item by handle without matching id check */ +_SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_at(_sfetch_pool_t* pool, uint32_t slot_id) { + SOKOL_ASSERT(pool && pool->valid); + uint32_t slot_index = _sfetch_slot_index(slot_id); + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return &pool->items[slot_index]; +} + +/* return pointer to item by handle with matching id check */ +_SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_lookup(_sfetch_pool_t* pool, uint32_t slot_id) { + SOKOL_ASSERT(pool && pool->valid); + if (0 != slot_id) { + _sfetch_item_t* item = _sfetch_pool_item_at(pool, slot_id); + if (item->handle.id == slot_id) { + return item; + } + } + return 0; +} + +// ██████ ██████ ███████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ███████ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ███████ ██ ██ ██ +// +// >>posix +#if _SFETCH_PLATFORM_POSIX +_SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) { + return fopen(path->buf, "rb"); +} + +_SOKOL_PRIVATE void _sfetch_file_close(_sfetch_file_handle_t h) { + fclose(h); +} + +_SOKOL_PRIVATE bool _sfetch_file_handle_valid(_sfetch_file_handle_t h) { + return h != _SFETCH_INVALID_FILE_HANDLE; +} + +_SOKOL_PRIVATE uint32_t _sfetch_file_size(_sfetch_file_handle_t h) { + fseek(h, 0, SEEK_END); + return (uint32_t) ftell(h); +} + +_SOKOL_PRIVATE bool _sfetch_file_read(_sfetch_file_handle_t h, uint32_t offset, uint32_t num_bytes, void* ptr) { + fseek(h, (long)offset, SEEK_SET); + return num_bytes == fread(ptr, 1, num_bytes, h); +} + +_SOKOL_PRIVATE bool _sfetch_thread_init(_sfetch_thread_t* thread, _sfetch_thread_func_t thread_func, void* thread_arg) { + SOKOL_ASSERT(thread && !thread->valid && !thread->stop_requested); + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->incoming_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->outgoing_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->running_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->stop_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_condattr_t cond_attr; + pthread_condattr_init(&cond_attr); + pthread_cond_init(&thread->incoming_cond, &cond_attr); + pthread_condattr_destroy(&cond_attr); + + /* FIXME: in debug mode, the threads should be named */ + pthread_mutex_lock(&thread->running_mutex); + int res = pthread_create(&thread->thread, 0, thread_func, thread_arg); + thread->valid = (0 == res); + pthread_mutex_unlock(&thread->running_mutex); + return thread->valid; +} + +_SOKOL_PRIVATE void _sfetch_thread_request_stop(_sfetch_thread_t* thread) { + pthread_mutex_lock(&thread->stop_mutex); + thread->stop_requested = true; + pthread_mutex_unlock(&thread->stop_mutex); +} + +_SOKOL_PRIVATE bool _sfetch_thread_stop_requested(_sfetch_thread_t* thread) { + pthread_mutex_lock(&thread->stop_mutex); + bool stop_requested = thread->stop_requested; + pthread_mutex_unlock(&thread->stop_mutex); + return stop_requested; +} + +_SOKOL_PRIVATE void _sfetch_thread_join(_sfetch_thread_t* thread) { + SOKOL_ASSERT(thread); + if (thread->valid) { + pthread_mutex_lock(&thread->incoming_mutex); + _sfetch_thread_request_stop(thread); + pthread_cond_signal(&thread->incoming_cond); + pthread_mutex_unlock(&thread->incoming_mutex); + pthread_join(thread->thread, 0); + thread->valid = false; + } + pthread_mutex_destroy(&thread->stop_mutex); + pthread_mutex_destroy(&thread->running_mutex); + pthread_mutex_destroy(&thread->incoming_mutex); + pthread_mutex_destroy(&thread->outgoing_mutex); + pthread_cond_destroy(&thread->incoming_cond); +} + +/* called when the thread-func is entered, this blocks the thread func until + the _sfetch_thread_t object is fully initialized +*/ +_SOKOL_PRIVATE void _sfetch_thread_entered(_sfetch_thread_t* thread) { + pthread_mutex_lock(&thread->running_mutex); +} + +/* called by the thread-func right before it is left */ +_SOKOL_PRIVATE void _sfetch_thread_leaving(_sfetch_thread_t* thread) { + pthread_mutex_unlock(&thread->running_mutex); +} + +_SOKOL_PRIVATE void _sfetch_thread_enqueue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming, _sfetch_ring_t* src) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + SOKOL_ASSERT(src && src->buf); + if (!_sfetch_ring_empty(src)) { + pthread_mutex_lock(&thread->incoming_mutex); + while (!_sfetch_ring_full(incoming) && !_sfetch_ring_empty(src)) { + _sfetch_ring_enqueue(incoming, _sfetch_ring_dequeue(src)); + } + pthread_cond_signal(&thread->incoming_cond); + pthread_mutex_unlock(&thread->incoming_mutex); + } +} + +_SOKOL_PRIVATE uint32_t _sfetch_thread_dequeue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + pthread_mutex_lock(&thread->incoming_mutex); + while (_sfetch_ring_empty(incoming) && !thread->stop_requested) { + pthread_cond_wait(&thread->incoming_cond, &thread->incoming_mutex); + } + uint32_t item = 0; + if (!thread->stop_requested) { + item = _sfetch_ring_dequeue(incoming); + } + pthread_mutex_unlock(&thread->incoming_mutex); + return item; +} + +_SOKOL_PRIVATE bool _sfetch_thread_enqueue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, uint32_t item) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + SOKOL_ASSERT(0 != item); + pthread_mutex_lock(&thread->outgoing_mutex); + bool result = false; + if (!_sfetch_ring_full(outgoing)) { + _sfetch_ring_enqueue(outgoing, item); + } + pthread_mutex_unlock(&thread->outgoing_mutex); + return result; +} + +_SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, _sfetch_ring_t* dst) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + SOKOL_ASSERT(dst && dst->buf); + pthread_mutex_lock(&thread->outgoing_mutex); + while (!_sfetch_ring_full(dst) && !_sfetch_ring_empty(outgoing)) { + _sfetch_ring_enqueue(dst, _sfetch_ring_dequeue(outgoing)); + } + pthread_mutex_unlock(&thread->outgoing_mutex); +} +#endif /* _SFETCH_PLATFORM_POSIX */ + +// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ +// +// >>windows +#if _SFETCH_PLATFORM_WINDOWS +_SOKOL_PRIVATE bool _sfetch_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sfetch_clear(dst, (size_t)dst_num_bytes); + const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); + const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); + if ((dst_needed > 0) && (dst_needed < dst_chars)) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); + return true; + } else { + /* input string doesn't fit into destination buffer */ + return false; + } +} + +_SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) { + wchar_t w_path[SFETCH_MAX_PATH]; + if (!_sfetch_win32_utf8_to_wide(path->buf, w_path, sizeof(w_path))) { + _SFETCH_ERROR(FILE_PATH_UTF8_DECODING_FAILED); + return 0; + } + _sfetch_file_handle_t h = CreateFileW( + w_path, /* lpFileName */ + GENERIC_READ, /* dwDesiredAccess */ + FILE_SHARE_READ, /* dwShareMode */ + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, /* dwCreationDisposition */ + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, /* dwFlagsAndAttributes */ + NULL); /* hTemplateFile */ + return h; +} + +_SOKOL_PRIVATE void _sfetch_file_close(_sfetch_file_handle_t h) { + CloseHandle(h); +} + +_SOKOL_PRIVATE bool _sfetch_file_handle_valid(_sfetch_file_handle_t h) { + return h != _SFETCH_INVALID_FILE_HANDLE; +} + +_SOKOL_PRIVATE uint32_t _sfetch_file_size(_sfetch_file_handle_t h) { + return GetFileSize(h, NULL); +} + +_SOKOL_PRIVATE bool _sfetch_file_read(_sfetch_file_handle_t h, uint32_t offset, uint32_t num_bytes, void* ptr) { + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset; + BOOL seek_res = SetFilePointerEx(h, offset_li, NULL, FILE_BEGIN); + if (seek_res) { + DWORD bytes_read = 0; + BOOL read_res = ReadFile(h, ptr, (DWORD)num_bytes, &bytes_read, NULL); + return read_res && (bytes_read == num_bytes); + } else { + return false; + } +} + +_SOKOL_PRIVATE bool _sfetch_thread_init(_sfetch_thread_t* thread, _sfetch_thread_func_t thread_func, void* thread_arg) { + SOKOL_ASSERT(thread && !thread->valid && !thread->stop_requested); + + thread->incoming_event = CreateEventA(NULL, FALSE, FALSE, NULL); + SOKOL_ASSERT(NULL != thread->incoming_event); + InitializeCriticalSection(&thread->incoming_critsec); + InitializeCriticalSection(&thread->outgoing_critsec); + InitializeCriticalSection(&thread->running_critsec); + InitializeCriticalSection(&thread->stop_critsec); + + EnterCriticalSection(&thread->running_critsec); + const SIZE_T stack_size = 512 * 1024; + thread->thread = CreateThread(NULL, stack_size, thread_func, thread_arg, 0, NULL); + thread->valid = (NULL != thread->thread); + LeaveCriticalSection(&thread->running_critsec); + return thread->valid; +} + +_SOKOL_PRIVATE void _sfetch_thread_request_stop(_sfetch_thread_t* thread) { + EnterCriticalSection(&thread->stop_critsec); + thread->stop_requested = true; + LeaveCriticalSection(&thread->stop_critsec); +} + +_SOKOL_PRIVATE bool _sfetch_thread_stop_requested(_sfetch_thread_t* thread) { + EnterCriticalSection(&thread->stop_critsec); + bool stop_requested = thread->stop_requested; + LeaveCriticalSection(&thread->stop_critsec); + return stop_requested; +} + +_SOKOL_PRIVATE void _sfetch_thread_join(_sfetch_thread_t* thread) { + if (thread->valid) { + EnterCriticalSection(&thread->incoming_critsec); + _sfetch_thread_request_stop(thread); + BOOL set_event_res = SetEvent(thread->incoming_event); + _SOKOL_UNUSED(set_event_res); + SOKOL_ASSERT(set_event_res); + LeaveCriticalSection(&thread->incoming_critsec); + WaitForSingleObject(thread->thread, INFINITE); + CloseHandle(thread->thread); + thread->valid = false; + } + CloseHandle(thread->incoming_event); + DeleteCriticalSection(&thread->stop_critsec); + DeleteCriticalSection(&thread->running_critsec); + DeleteCriticalSection(&thread->outgoing_critsec); + DeleteCriticalSection(&thread->incoming_critsec); +} + +_SOKOL_PRIVATE void _sfetch_thread_entered(_sfetch_thread_t* thread) { + EnterCriticalSection(&thread->running_critsec); +} + +/* called by the thread-func right before it is left */ +_SOKOL_PRIVATE void _sfetch_thread_leaving(_sfetch_thread_t* thread) { + LeaveCriticalSection(&thread->running_critsec); +} + +_SOKOL_PRIVATE void _sfetch_thread_enqueue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming, _sfetch_ring_t* src) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + SOKOL_ASSERT(src && src->buf); + if (!_sfetch_ring_empty(src)) { + EnterCriticalSection(&thread->incoming_critsec); + while (!_sfetch_ring_full(incoming) && !_sfetch_ring_empty(src)) { + _sfetch_ring_enqueue(incoming, _sfetch_ring_dequeue(src)); + } + LeaveCriticalSection(&thread->incoming_critsec); + BOOL set_event_res = SetEvent(thread->incoming_event); + _SOKOL_UNUSED(set_event_res); + SOKOL_ASSERT(set_event_res); + } +} + +_SOKOL_PRIVATE uint32_t _sfetch_thread_dequeue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + EnterCriticalSection(&thread->incoming_critsec); + while (_sfetch_ring_empty(incoming) && !thread->stop_requested) { + LeaveCriticalSection(&thread->incoming_critsec); + WaitForSingleObject(thread->incoming_event, INFINITE); + EnterCriticalSection(&thread->incoming_critsec); + } + uint32_t item = 0; + if (!thread->stop_requested) { + item = _sfetch_ring_dequeue(incoming); + } + LeaveCriticalSection(&thread->incoming_critsec); + return item; +} + +_SOKOL_PRIVATE bool _sfetch_thread_enqueue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, uint32_t item) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + EnterCriticalSection(&thread->outgoing_critsec); + bool result = false; + if (!_sfetch_ring_full(outgoing)) { + _sfetch_ring_enqueue(outgoing, item); + } + LeaveCriticalSection(&thread->outgoing_critsec); + return result; +} + +_SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, _sfetch_ring_t* dst) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + SOKOL_ASSERT(dst && dst->buf); + EnterCriticalSection(&thread->outgoing_critsec); + while (!_sfetch_ring_full(dst) && !_sfetch_ring_empty(outgoing)) { + _sfetch_ring_enqueue(dst, _sfetch_ring_dequeue(outgoing)); + } + LeaveCriticalSection(&thread->outgoing_critsec); +} +#endif /* _SFETCH_PLATFORM_WINDOWS */ + +// ██████ ██ ██ █████ ███ ██ ███ ██ ███████ ██ ███████ +// ██ ██ ██ ██ ██ ████ ██ ████ ██ ██ ██ ██ +// ██ ███████ ███████ ██ ██ ██ ██ ██ ██ █████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ ████ ██ ████ ███████ ███████ ███████ +// +// >>channels + +/* per-channel request handler for native platforms accessing the local filesystem */ +#if _SFETCH_HAS_THREADS +_SOKOL_PRIVATE void _sfetch_request_handler(_sfetch_t* ctx, uint32_t slot_id) { + _sfetch_state_t state; + _sfetch_path_t* path; + _sfetch_item_thread_t* thread; + sfetch_range_t* buffer; + uint32_t chunk_size; + { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (!item) { + return; + } + state = item->state; + SOKOL_ASSERT((state == _SFETCH_STATE_FETCHING) || + (state == _SFETCH_STATE_PAUSED) || + (state == _SFETCH_STATE_FAILED)); + path = &item->path; + thread = &item->thread; + buffer = &item->buffer; + chunk_size = item->chunk_size; + } + if (thread->failed) { + return; + } + if (state == _SFETCH_STATE_FETCHING) { + if ((buffer->ptr == 0) || (buffer->size == 0)) { + thread->error_code = SFETCH_ERROR_NO_BUFFER; + thread->failed = true; + } else { + /* open file if not happened yet */ + if (!_sfetch_file_handle_valid(thread->file_handle)) { + SOKOL_ASSERT(path->buf[0]); + SOKOL_ASSERT(thread->fetched_offset == 0); + SOKOL_ASSERT(thread->fetched_size == 0); + thread->file_handle = _sfetch_file_open(path); + if (_sfetch_file_handle_valid(thread->file_handle)) { + thread->content_size = _sfetch_file_size(thread->file_handle); + } else { + thread->error_code = SFETCH_ERROR_FILE_NOT_FOUND; + thread->failed = true; + } + } + if (!thread->failed) { + uint32_t read_offset = 0; + uint32_t bytes_to_read = 0; + if (chunk_size == 0) { + /* load entire file */ + if (thread->content_size <= buffer->size) { + bytes_to_read = thread->content_size; + read_offset = 0; + } else { + /* provided buffer to small to fit entire file */ + thread->error_code = SFETCH_ERROR_BUFFER_TOO_SMALL; + thread->failed = true; + } + } else { + if (chunk_size <= buffer->size) { + bytes_to_read = chunk_size; + read_offset = thread->fetched_offset; + if ((read_offset + bytes_to_read) > thread->content_size) { + bytes_to_read = thread->content_size - read_offset; + } + } else { + /* provided buffer to small to fit next chunk */ + thread->error_code = SFETCH_ERROR_BUFFER_TOO_SMALL; + thread->failed = true; + } + } + if (!thread->failed) { + if (_sfetch_file_read(thread->file_handle, read_offset, bytes_to_read, (void*)buffer->ptr)) { + thread->fetched_size = bytes_to_read; + thread->fetched_offset += bytes_to_read; + } else { + thread->error_code = SFETCH_ERROR_UNEXPECTED_EOF; + thread->failed = true; + } + } + } + } + SOKOL_ASSERT(thread->fetched_offset <= thread->content_size); + if (thread->failed || (thread->fetched_offset == thread->content_size)) { + if (_sfetch_file_handle_valid(thread->file_handle)) { + _sfetch_file_close(thread->file_handle); + thread->file_handle = _SFETCH_INVALID_FILE_HANDLE; + } + thread->finished = true; + } + } + /* ignore items in PAUSED or FAILED state */ +} + +#if _SFETCH_PLATFORM_WINDOWS +_SOKOL_PRIVATE DWORD WINAPI _sfetch_channel_thread_func(LPVOID arg) { +#else +_SOKOL_PRIVATE void* _sfetch_channel_thread_func(void* arg) { +#endif + _sfetch_channel_t* chn = (_sfetch_channel_t*) arg; + _sfetch_thread_entered(&chn->thread); + while (!_sfetch_thread_stop_requested(&chn->thread)) { + /* block until work arrives */ + uint32_t slot_id = _sfetch_thread_dequeue_incoming(&chn->thread, &chn->thread_incoming); + /* slot_id will be invalid if the thread was woken up to join */ + if (!_sfetch_thread_stop_requested(&chn->thread)) { + SOKOL_ASSERT(0 != slot_id); + chn->request_handler(chn->ctx, slot_id); + SOKOL_ASSERT(!_sfetch_ring_full(&chn->thread_outgoing)); + _sfetch_thread_enqueue_outgoing(&chn->thread, &chn->thread_outgoing, slot_id); + } + } + _sfetch_thread_leaving(&chn->thread); + return 0; +} +#endif /* _SFETCH_HAS_THREADS */ + +#if _SFETCH_PLATFORM_EMSCRIPTEN +EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cstr), { + const path_str = UTF8ToString(path_cstr); + fetch(path_str, { method: 'HEAD' }).then((response) => { + if (response.ok) { + const content_length = response.headers.get('Content-Length'); + if (content_length === null) { + console.warn(`sokol_fetch.h: HEAD ${path_str} response has no Content-Length`); + __sfetch_emsc_failed_other(slot_id); + } else { + __sfetch_emsc_head_response(slot_id, Number(content_length)); + } + } else { + __sfetch_emsc_failed_http_status(slot_id, response.status); + } + }).catch((err) => { + console.error(`sokol_fetch.h: HEAD ${path_str} failed with: `, err); + __sfetch_emsc_failed_other(slot_id); + }); +}) + +/* if bytes_to_read != 0, a range-request will be sent, otherwise a normal request */ +EM_JS(void, sfetch_js_send_get_request, (uint32_t slot_id, const char* path_cstr, uint32_t offset, uint32_t bytes_to_read, void* buf_ptr, uint32_t buf_size), { + const path_str = UTF8ToString(path_cstr); + const headers = new Headers(); + const range_request = bytes_to_read > 0; + if (range_request) { + headers.append('Range', `bytes=${offset}-${offset+bytes_to_read-1}`); + } + fetch(path_str, { method: 'GET', headers }).then((response) => { + if (response.ok) { + response.arrayBuffer().then((data) => { + const u8_data = new Uint8Array(data); + if (u8_data.length <= buf_size) { + HEAPU8.set(u8_data, buf_ptr); + __sfetch_emsc_get_response(slot_id, bytes_to_read, u8_data.length); + } else { + __sfetch_emsc_failed_buffer_too_small(slot_id); + } + }).catch((err) => { + console.error(`sokol_fetch.h: GET ${path_str} failed with: `, err); + __sfetch_emsc_failed_other(slot_id); + }); + } else { + __sfetch_emsc_failed_http_status(slot_id, response.status); + } + }).catch((err) => { + console.error(`sokol_fetch.h: GET ${path_str} failed with: `, err); + __sfetch_emsc_failed_other(slot_id); + }); +}) + +/*=== emscripten specific C helper functions =================================*/ +#ifdef __cplusplus +extern "C" { +#endif +void _sfetch_emsc_send_get_request(uint32_t slot_id, _sfetch_item_t* item) { + if ((item->buffer.ptr == 0) || (item->buffer.size == 0)) { + item->thread.error_code = SFETCH_ERROR_NO_BUFFER; + item->thread.failed = true; + } else { + uint32_t offset = 0; + uint32_t bytes_to_read = 0; + if (item->chunk_size > 0) { + /* send HTTP range request */ + SOKOL_ASSERT(item->thread.content_size > 0); + SOKOL_ASSERT(item->thread.http_range_offset < item->thread.content_size); + bytes_to_read = item->thread.content_size - item->thread.http_range_offset; + if (bytes_to_read > item->chunk_size) { + bytes_to_read = item->chunk_size; + } + SOKOL_ASSERT(bytes_to_read > 0); + offset = item->thread.http_range_offset; + } + sfetch_js_send_get_request(slot_id, item->path.buf, offset, bytes_to_read, (void*)item->buffer.ptr, item->buffer.size); + } +} + +/* called by JS when an initial HEAD request finished successfully (only when streaming chunks) */ +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_head_response(uint32_t slot_id, uint32_t content_length) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + SOKOL_ASSERT(item->buffer.ptr && (item->buffer.size > 0)); + item->thread.content_size = content_length; + _sfetch_emsc_send_get_request(slot_id, item); + } + } +} + +/* called by JS when a followup GET request finished successfully */ +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_get_response(uint32_t slot_id, uint32_t range_fetched_size, uint32_t content_fetched_size) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + item->thread.fetched_size = content_fetched_size; + item->thread.fetched_offset += content_fetched_size; + item->thread.http_range_offset += range_fetched_size; + if (item->chunk_size == 0) { + item->thread.finished = true; + } else if (item->thread.http_range_offset >= item->thread.content_size) { + item->thread.finished = true; + } + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +/* called by JS when an error occurred */ +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_http_status(uint32_t slot_id, uint32_t http_status) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + if (http_status == 404) { + item->thread.error_code = SFETCH_ERROR_FILE_NOT_FOUND; + } else { + item->thread.error_code = SFETCH_ERROR_INVALID_HTTP_STATUS; + } + item->thread.failed = true; + item->thread.finished = true; + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_buffer_too_small(uint32_t slot_id) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + item->thread.error_code = SFETCH_ERROR_BUFFER_TOO_SMALL; + item->thread.failed = true; + item->thread.finished = true; + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_other(uint32_t slot_id) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + item->thread.error_code = SFETCH_ERROR_JS_OTHER; + item->thread.failed = true; + item->thread.finished = true; + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +_SOKOL_PRIVATE void _sfetch_request_handler(_sfetch_t* ctx, uint32_t slot_id) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (!item) { + return; + } + if (item->state == _SFETCH_STATE_FETCHING) { + if ((item->chunk_size > 0) && (item->thread.content_size == 0)) { + /* if streaming download is requested, and the content-length isn't known + yet, need to send a HEAD request first + */ + sfetch_js_send_head_request(slot_id, item->path.buf); + } else { + /* otherwise, this is either a request to load the entire file, or + to load the next streaming chunk + */ + _sfetch_emsc_send_get_request(slot_id, item); + } + } else { + /* just move all other items (e.g. paused or cancelled) + into the outgoing queue, so they won't get lost + */ + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + if (item->thread.failed) { + item->thread.finished = true; + } +} +#endif /* _SFETCH_PLATFORM_EMSCRIPTEN */ + +_SOKOL_PRIVATE void _sfetch_channel_discard(_sfetch_channel_t* chn) { + SOKOL_ASSERT(chn); + #if _SFETCH_HAS_THREADS + if (chn->valid) { + _sfetch_thread_join(&chn->thread); + } + _sfetch_ring_discard(&chn->thread_incoming); + _sfetch_ring_discard(&chn->thread_outgoing); + #endif + _sfetch_ring_discard(&chn->free_lanes); + _sfetch_ring_discard(&chn->user_sent); + _sfetch_ring_discard(&chn->user_incoming); + _sfetch_ring_discard(&chn->user_outgoing); + _sfetch_ring_discard(&chn->free_lanes); + chn->valid = false; +} + +_SOKOL_PRIVATE bool _sfetch_channel_init(_sfetch_channel_t* chn, _sfetch_t* ctx, uint32_t num_items, uint32_t num_lanes, void (*request_handler)(_sfetch_t* ctx, uint32_t)) { + SOKOL_ASSERT(chn && (num_items > 0) && request_handler); + SOKOL_ASSERT(!chn->valid); + bool valid = true; + chn->request_handler = request_handler; + chn->ctx = ctx; + valid &= _sfetch_ring_init(&chn->free_lanes, num_lanes); + for (uint32_t lane = 0; lane < num_lanes; lane++) { + _sfetch_ring_enqueue(&chn->free_lanes, lane); + } + valid &= _sfetch_ring_init(&chn->user_sent, num_items); + valid &= _sfetch_ring_init(&chn->user_incoming, num_lanes); + valid &= _sfetch_ring_init(&chn->user_outgoing, num_lanes); + #if _SFETCH_HAS_THREADS + valid &= _sfetch_ring_init(&chn->thread_incoming, num_lanes); + valid &= _sfetch_ring_init(&chn->thread_outgoing, num_lanes); + #endif + if (valid) { + chn->valid = true; + #if _SFETCH_HAS_THREADS + _sfetch_thread_init(&chn->thread, _sfetch_channel_thread_func, chn); + #endif + return true; + } else { + _sfetch_channel_discard(chn); + return false; + } +} + +/* put a request into the channels sent-queue, this is where all new requests + are stored until a lane becomes free. +*/ +_SOKOL_PRIVATE bool _sfetch_channel_send(_sfetch_channel_t* chn, uint32_t slot_id) { + SOKOL_ASSERT(chn && chn->valid); + if (!_sfetch_ring_full(&chn->user_sent)) { + _sfetch_ring_enqueue(&chn->user_sent, slot_id); + return true; + } else { + _SFETCH_ERROR(SEND_QUEUE_FULL); + return false; + } +} + +_SOKOL_PRIVATE void _sfetch_invoke_response_callback(_sfetch_item_t* item) { + sfetch_response_t response; + _sfetch_clear(&response, sizeof(response)); + response.handle = item->handle; + response.dispatched = (item->state == _SFETCH_STATE_DISPATCHED); + response.fetched = (item->state == _SFETCH_STATE_FETCHED); + response.paused = (item->state == _SFETCH_STATE_PAUSED); + response.finished = item->user.finished; + response.failed = (item->state == _SFETCH_STATE_FAILED); + response.cancelled = item->user.cancel; + response.error_code = item->user.error_code; + response.channel = item->channel; + response.lane = item->lane; + response.path = item->path.buf; + response.user_data = item->user.user_data; + response.data_offset = item->user.fetched_offset - item->user.fetched_size; + response.data.ptr = item->buffer.ptr; + response.data.size = item->user.fetched_size; + response.buffer = item->buffer; + item->callback(&response); +} + +_SOKOL_PRIVATE void _sfetch_cancel_item(_sfetch_item_t* item) { + item->state = _SFETCH_STATE_FAILED; + item->user.finished = true; + item->user.error_code = SFETCH_ERROR_CANCELLED; +} + +/* per-frame channel stuff: move requests in and out of the IO threads, call response callbacks */ +_SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_t* pool) { + + /* move items from sent- to incoming-queue permitting free lanes */ + const uint32_t num_sent = _sfetch_ring_count(&chn->user_sent); + const uint32_t avail_lanes = _sfetch_ring_count(&chn->free_lanes); + const uint32_t num_move = (num_sent < avail_lanes) ? num_sent : avail_lanes; + for (uint32_t i = 0; i < num_move; i++) { + const uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_sent); + _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id); + SOKOL_ASSERT(item); + SOKOL_ASSERT(item->state == _SFETCH_STATE_ALLOCATED); + // if the item was cancelled early, kick it out immediately + if (item->user.cancel) { + _sfetch_cancel_item(item); + _sfetch_invoke_response_callback(item); + _sfetch_pool_item_free(pool, slot_id); + continue; + } + item->state = _SFETCH_STATE_DISPATCHED; + item->lane = _sfetch_ring_dequeue(&chn->free_lanes); + // if no buffer provided yet, invoke response callback to do so + if (0 == item->buffer.ptr) { + _sfetch_invoke_response_callback(item); + } + _sfetch_ring_enqueue(&chn->user_incoming, slot_id); + } + + /* prepare incoming items for being moved into the IO thread */ + const uint32_t num_incoming = _sfetch_ring_count(&chn->user_incoming); + for (uint32_t i = 0; i < num_incoming; i++) { + const uint32_t slot_id = _sfetch_ring_peek(&chn->user_incoming, i); + _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id); + SOKOL_ASSERT(item); + SOKOL_ASSERT(item->state != _SFETCH_STATE_INITIAL); + SOKOL_ASSERT(item->state != _SFETCH_STATE_FETCHING); + /* transfer input params from user- to thread-data */ + if (item->user.pause) { + item->state = _SFETCH_STATE_PAUSED; + item->user.pause = false; + } + if (item->user.cont) { + if (item->state == _SFETCH_STATE_PAUSED) { + item->state = _SFETCH_STATE_FETCHED; + } + item->user.cont = false; + } + if (item->user.cancel) { + _sfetch_cancel_item(item); + } + switch (item->state) { + case _SFETCH_STATE_DISPATCHED: + case _SFETCH_STATE_FETCHED: + item->state = _SFETCH_STATE_FETCHING; + break; + default: break; + } + } + + #if _SFETCH_HAS_THREADS + /* move new items into the IO threads and processed items out of IO threads */ + _sfetch_thread_enqueue_incoming(&chn->thread, &chn->thread_incoming, &chn->user_incoming); + _sfetch_thread_dequeue_outgoing(&chn->thread, &chn->thread_outgoing, &chn->user_outgoing); + #else + /* without threading just directly dequeue items from the user_incoming queue and + call the request handler, the user_outgoing queue will be filled as the + asynchronous HTTP requests sent by the request handler are completed + */ + while (!_sfetch_ring_empty(&chn->user_incoming)) { + uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_incoming); + _sfetch_request_handler(chn->ctx, slot_id); + } + #endif + + /* drain the outgoing queue, prepare items for invoking the response + callback, and finally call the response callback, free finished items + */ + while (!_sfetch_ring_empty(&chn->user_outgoing)) { + const uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_outgoing); + SOKOL_ASSERT(slot_id); + _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id); + SOKOL_ASSERT(item && item->callback); + SOKOL_ASSERT(item->state != _SFETCH_STATE_INITIAL); + SOKOL_ASSERT(item->state != _SFETCH_STATE_ALLOCATED); + SOKOL_ASSERT(item->state != _SFETCH_STATE_DISPATCHED); + SOKOL_ASSERT(item->state != _SFETCH_STATE_FETCHED); + /* transfer output params from thread- to user-data */ + item->user.fetched_offset = item->thread.fetched_offset; + item->user.fetched_size = item->thread.fetched_size; + if (item->user.cancel) { + _sfetch_cancel_item(item); + } else { + item->user.error_code = item->thread.error_code; + } + if (item->thread.finished) { + item->user.finished = true; + } + /* state transition */ + if (item->thread.failed) { + item->state = _SFETCH_STATE_FAILED; + } else if (item->state == _SFETCH_STATE_FETCHING) { + item->state = _SFETCH_STATE_FETCHED; + } + _sfetch_invoke_response_callback(item); + + /* when the request is finished, free the lane for another request, + otherwise feed it back into the incoming queue + */ + if (item->user.finished) { + _sfetch_ring_enqueue(&chn->free_lanes, item->lane); + _sfetch_pool_item_free(pool, slot_id); + } else { + _sfetch_ring_enqueue(&chn->user_incoming, slot_id); + } + } +} + +_SOKOL_PRIVATE bool _sfetch_validate_request(_sfetch_t* ctx, const sfetch_request_t* req) { + if (req->channel >= ctx->desc.num_channels) { + _SFETCH_ERROR(REQUEST_CHANNEL_INDEX_TOO_BIG); + return false; + } + if (!req->path) { + _SFETCH_ERROR(REQUEST_PATH_IS_NULL); + return false; + } + if (strlen(req->path) >= (SFETCH_MAX_PATH-1)) { + _SFETCH_ERROR(REQUEST_PATH_TOO_LONG); + return false; + } + if (!req->callback) { + _SFETCH_ERROR(REQUEST_CALLBACK_MISSING); + return false; + } + if (req->chunk_size > req->buffer.size) { + _SFETCH_ERROR(REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE); + return false; + } + if (req->user_data.ptr && (req->user_data.size == 0)) { + _SFETCH_ERROR(REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL); + return false; + } + if (!req->user_data.ptr && (req->user_data.size > 0)) { + _SFETCH_ERROR(REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT); + return false; + } + if (req->user_data.size > SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) { + _SFETCH_ERROR(REQUEST_USERDATA_SIZE_TOO_BIG); + return false; + } + return true; +} + +_SOKOL_PRIVATE sfetch_desc_t _sfetch_desc_defaults(const sfetch_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sfetch_desc_t res = *desc; + res.max_requests = _sfetch_def(desc->max_requests, 128); + res.num_channels = _sfetch_def(desc->num_channels, 1); + res.num_lanes = _sfetch_def(desc->num_lanes, 1); + return res; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc_) { + SOKOL_ASSERT(desc_); + SOKOL_ASSERT(0 == _sfetch); + + sfetch_desc_t desc = _sfetch_desc_defaults(desc_); + _sfetch = (_sfetch_t*) _sfetch_malloc_with_allocator(&desc.allocator, sizeof(_sfetch_t)); + SOKOL_ASSERT(_sfetch); + _sfetch_t* ctx = _sfetch_ctx(); + _sfetch_clear(ctx, sizeof(_sfetch_t)); + ctx->desc = desc; + ctx->setup = true; + ctx->valid = true; + + /* replace zero-init items with default values */ + if (ctx->desc.num_channels > SFETCH_MAX_CHANNELS) { + ctx->desc.num_channels = SFETCH_MAX_CHANNELS; + _SFETCH_WARN(CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS); + } + + /* setup the global request item pool */ + ctx->valid &= _sfetch_pool_init(&ctx->pool, ctx->desc.max_requests); + + /* setup IO channels (one thread per channel) */ + for (uint32_t i = 0; i < ctx->desc.num_channels; i++) { + ctx->valid &= _sfetch_channel_init(&ctx->chn[i], ctx, ctx->desc.max_requests, ctx->desc.num_lanes, _sfetch_request_handler); + } +} + +SOKOL_API_IMPL void sfetch_shutdown(void) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->setup); + ctx->valid = false; + /* IO threads must be shutdown first */ + for (uint32_t i = 0; i < ctx->desc.num_channels; i++) { + if (ctx->chn[i].valid) { + _sfetch_channel_discard(&ctx->chn[i]); + } + } + _sfetch_pool_discard(&ctx->pool); + ctx->setup = false; + _sfetch_free(ctx); + _sfetch = 0; +} + +SOKOL_API_IMPL bool sfetch_valid(void) { + _sfetch_t* ctx = _sfetch_ctx(); + return ctx && ctx->valid; +} + +SOKOL_API_IMPL sfetch_desc_t sfetch_desc(void) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + return ctx->desc; +} + +SOKOL_API_IMPL int sfetch_max_userdata_bytes(void) { + return SFETCH_MAX_USERDATA_UINT64 * 8; +} + +SOKOL_API_IMPL int sfetch_max_path(void) { + return SFETCH_MAX_PATH; +} + +SOKOL_API_IMPL bool sfetch_handle_valid(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + /* shortcut invalid handle */ + if (h.id == 0) { + return false; + } + return 0 != _sfetch_pool_item_lookup(&ctx->pool, h.id); +} + +SOKOL_API_IMPL sfetch_handle_t sfetch_send(const sfetch_request_t* request) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->setup); + + const sfetch_handle_t invalid_handle = _sfetch_make_handle(0); + if (!ctx->valid) { + return invalid_handle; + } + if (!_sfetch_validate_request(ctx, request)) { + return invalid_handle; + } + SOKOL_ASSERT(request->channel < ctx->desc.num_channels); + + uint32_t slot_id = _sfetch_pool_item_alloc(&ctx->pool, request); + if (0 == slot_id) { + _SFETCH_WARN(REQUEST_POOL_EXHAUSTED); + return invalid_handle; + } + if (!_sfetch_channel_send(&ctx->chn[request->channel], slot_id)) { + /* send failed because the channels sent-queue overflowed */ + _sfetch_pool_item_free(&ctx->pool, slot_id); + return invalid_handle; + } + return _sfetch_make_handle(slot_id); +} + +SOKOL_API_IMPL void sfetch_dowork(void) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->setup); + if (!ctx->valid) { + return; + } + /* we're pumping each channel 2x so that unfinished request items coming out the + IO threads can be moved back into the IO-thread immediately without + having to wait a frame + */ + ctx->in_callback = true; + for (int pass = 0; pass < 2; pass++) { + for (uint32_t chn_index = 0; chn_index < ctx->desc.num_channels; chn_index++) { + _sfetch_channel_dowork(&ctx->chn[chn_index], &ctx->pool); + } + } + ctx->in_callback = false; +} + +SOKOL_API_IMPL void sfetch_bind_buffer(sfetch_handle_t h, sfetch_range_t buffer) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + SOKOL_ASSERT(ctx->in_callback); + SOKOL_ASSERT(buffer.ptr && (buffer.size > 0)); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + SOKOL_ASSERT((0 == item->buffer.ptr) && (0 == item->buffer.size)); + item->buffer = buffer; + } +} + +SOKOL_API_IMPL void* sfetch_unbind_buffer(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + SOKOL_ASSERT(ctx->in_callback); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + void* prev_buf_ptr = (void*)item->buffer.ptr; + item->buffer.ptr = 0; + item->buffer.size = 0; + return prev_buf_ptr; + } else { + return 0; + } +} + +SOKOL_API_IMPL void sfetch_pause(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + item->user.pause = true; + item->user.cont = false; + } +} + +SOKOL_API_IMPL void sfetch_continue(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + item->user.cont = true; + item->user.pause = false; + } +} + +SOKOL_API_IMPL void sfetch_cancel(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + item->user.cont = false; + item->user.pause = false; + item->user.cancel = true; + } +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif /* SOKOL_FETCH_IMPL */ diff --git a/modules/sokol-jai/sokol/c/sokol_fontstash.c b/modules/sokol-jai/sokol/c/sokol_fontstash.c new file mode 100644 index 0000000..087acd5 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_fontstash.c @@ -0,0 +1,8 @@ +#include "sokol_gfx.h" +#include "sokol_gl.h" + +#define FONTSTASH_IMPLEMENTATION +#include +#include "fontstash.h" +#define SOKOL_IMPL +#include "sokol_fontstash.h" diff --git a/modules/sokol-jai/sokol/c/sokol_fontstash.h b/modules/sokol-jai/sokol/c/sokol_fontstash.h new file mode 100644 index 0000000..27a48c8 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_fontstash.h @@ -0,0 +1,2272 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_IMPL +#endif +#ifndef SOKOL_FONTSTASH_INCLUDED +/* + sokol_fontstash.h -- renderer for https://github.com/memononen/fontstash + on top of sokol_gl.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_FONTSTASH_IMPL + + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_FONTSTASH_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_FONTSTASH_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + Include the following headers before including sokol_fontstash.h: + + sokol_gfx.h + + Additionally include the following headers for including the sokol_fontstash.h + implementation: + + sokol_gl.h + + HOW TO + ====== + --- First initialize sokol-gfx and sokol-gl as usual: + + sg_setup(&(sg_desc){...}); + sgl_setup(&(sgl_desc){...}); + + --- Create at least one fontstash context with sfons_create() (this replaces + glfonsCreate() from fontstash.h's example GL renderer: + + FONScontext* ctx = sfons_create(&(sfons_desc_t){ + .width = atlas_width, + .height = atlas_height, + }); + + Each FONScontext manages one font atlas texture which can hold rasterized + glyphs for multiple fonts. + + --- From here on, use fontstash.h's functions "as usual" to add TTF + font data and draw text. Note that (just like with sokol-gl), text + rendering can happen anywhere in the frame, not only inside + a sokol-gfx rendering pass. + + --- You can use the helper function + + uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + + To convert a 0..255 RGBA color into a packed uint32_t color value + expected by fontstash.h. + + --- Once per frame before calling sgl_draw(), call: + + sfons_flush(FONScontext* ctx) + + ...this will update the dynamic sokol-gfx texture with the latest font + atlas content. + + --- To actually render the text (and any other sokol-gl draw commands), + call sgl_draw() inside a sokol-gfx frame. + + --- NOTE that you can mix fontstash.h calls with sokol-gl calls to mix + text rendering with sokol-gl rendering. You can also use + sokol-gl's matrix stack to position fontstash.h text in 3D. + + --- finally on application shutdown, call: + + sfons_destroy(FONScontext* ctx) + + before sgl_shutdown() and sg_shutdown() + + + WHAT HAPPENS UNDER THE HOOD: + ============================ + + FONScontext* sfons_create(const sfons_desc_t* desc) + - creates a sokol-gfx shader compatible with sokol-gl + - creates an sgl_pipeline object with alpha-blending using + this shader + - creates a 1-byte-per-pixel font atlas texture via sokol-gfx + (pixel format SG_PIXELFORMAT_R8) + + fonsDrawText(): + - this will call the following sequence of sokol-gl functions: + + sgl_enable_texture(); + sgl_texture(...); + sgl_push_pipeline(); + sgl_load_pipeline(...); + sgl_begin_triangles(); + for each vertex: + sgl_v2f_t2f_c1i(...); + sgl_end(); + sgl_pop_pipeline(); + sgl_disable_texture(); + + - note that sokol-gl will merge several sgl_*_begin/sgl_end pairs + into a single draw call if no relevant state has changed, typically + all calls to fonsDrawText() will be merged into a single draw call + as long as all calls use the same FONScontext + + sfons_flush(FONScontext* ctx): + - this will call sg_update_image() on the font atlas texture + if fontstash.h has added any rasterized glyphs since the last + frame + + sfons_destroy(FONScontext* ctx): + - destroy the font atlas texture, sgl_pipeline and sg_shader objects + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + FONScontext* fons_context = sfons_create(&(sfons_desc_t){ + ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. Please + note that this doesn't affect any memory allocation performed + in fontstash.h (unfortunately those are hardwired to malloc/free). + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_FONTSTASH_INCLUDED (1) +#include +#include +#include // size_t + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_fontstash.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_FONTSTASH_API_DECL) +#define SOKOL_FONTSTASH_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_FONTSTASH_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllimport) +#else +#define SOKOL_FONTSTASH_API_DECL extern +#endif +#endif +#ifdef __cplusplus +extern "C" { +#endif + +/* + sfonst_allocator_t + + Used in sfons_desc_t to provide custom memory-alloc and -free functions + to sokol_fontstash.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). + + NOTE that this does not affect memory allocation calls inside + fontstash.h +*/ +typedef struct sfons_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sfons_allocator_t; + +typedef struct sfons_desc_t { + int width; // initial width of font atlas texture (default: 512, must be power of 2) + int height; // initial height of font atlas texture (default: 512, must be power of 2) + sfons_allocator_t allocator; // optional memory allocation overrides +} sfons_desc_t; + +SOKOL_FONTSTASH_API_DECL FONScontext* sfons_create(const sfons_desc_t* desc); +SOKOL_FONTSTASH_API_DECL void sfons_destroy(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL void sfons_flush(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_FONTSTASH_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_FONTSTASH_IMPL +#define SOKOL_FONTSTASH_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sfons_desc_t.allocator to override memory allocation functions" +#endif + +#include // memset, memcpy +#include // malloc, free + +#if !defined(SOKOL_GL_INCLUDED) +#error "Please include sokol_gl.h before sokol_fontstash.h" +#endif +#if !defined(FONS_H) +#error "Please include fontstash.h before sokol_fontstash.h" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +/* + Embedded source code compiled with: + + sokol-shdc -i sfons.glsl -o sfons.h -l glsl410:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + layout(binding=0) uniform vs_params { + uniform mat4 mvp; + uniform mat4 tm; + }; + in vec4 position; + in vec2 texcoord0; + in vec4 color0; + in float psize; + out vec4 uv; + out vec4 color; + void main() { + gl_Position = mvp * position; + #ifndef SOKOL_WGSL + gl_PointSize = psize; + #endif + uv = tm * vec4(texcoord0, 0.0, 1.0); + color = color0; + } + @end + + @fs fs + layout(binding=0) uniform texture2D tex; + layout(binding=0) uniform sampler smp; + in vec4 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = vec4(1.0, 1.0, 1.0, texture(sampler2D(tex, smp), uv.xy).r) * color; + } + @end + + @program sfontstash vs fs +*/ +#if defined(SOKOL_GLCORE) +/* + #version 410 + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + layout(location = 0) out vec4 uv; + layout(location = 1) in vec2 texcoord0; + layout(location = 1) out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sfons_vs_source_glsl410[520] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, + 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76, + 0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x31,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53, + 0x69,0x7a,0x65,0x20,0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37, + 0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f, + 0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 410 + + uniform sampler2D tex_smp; + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 uv; + layout(location = 1) in vec4 color; + + void main() + { + frag_color = vec4(1.0, 1.0, 1.0, texture(tex_smp, uv.xy).x) * color; + } +*/ +static const uint8_t _sfons_fs_source_glsl410[245] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f, + 0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76, + 0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES3) +/* + #version 300 es + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + out vec4 uv; + layout(location = 1) in vec2 texcoord0; + out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sfons_vs_source_glsl300es[481] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74, + 0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b, + 0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20, + 0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x70, + 0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d, + 0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65, + 0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D tex_smp; + + layout(location = 0) out highp vec4 frag_color; + in highp vec4 uv; + in highp vec4 color; + + void main() + { + frag_color = vec4(1.0, 1.0, 1.0, texture(tex_smp, uv.xy).x) * color; + } + +*/ +static const uint8_t _sfons_fs_source_glsl300es[276] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75, + 0x76,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e, + 0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sfons_vs_bytecode_metal_macos[3381] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x35,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x59,0xf1,0xe4,0x22,0x0f,0x75,0x42, + 0x53,0x9c,0x2a,0x05,0xe4,0xbd,0x91,0x28,0x82,0x06,0x3f,0x0d,0xb9,0xef,0x34,0xbd, + 0xe8,0xfa,0x46,0x5c,0x66,0x1f,0xc6,0xde,0x3a,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x04,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xfe,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x68,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x18,0x42,0x01,0x2c,0x40, + 0x05,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79, + 0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5, + 0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07, + 0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0, + 0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07, + 0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, + 0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, + 0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60, + 0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f, + 0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79, + 0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, + 0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07, + 0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, + 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50, + 0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07, + 0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, + 0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07, + 0x72,0x30,0x84,0x49,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8,0x02,0x01,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, + 0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50, + 0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00,0x88,0x8d,0x25,0x3c,0x00,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x01,0x01,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x61,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89, + 0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19, + 0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7, + 0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70, + 0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5, + 0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28, + 0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71,0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b, + 0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6,0x26,0x37,0x84,0x51,0x1e,0xc5,0x52, + 0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26,0xe7,0x02,0xf7,0x36,0x97,0x46,0x97, + 0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88, + 0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74,0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2, + 0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98,0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63, + 0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3,0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29, + 0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c,0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c, + 0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34, + 0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c, + 0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,0x28,0x5f,0x5f,0x61,0x69,0x72, + 0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c,0x64,0x65,0x72,0x5f,0x5f,0x29,0x44, + 0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x84,0x81,0x22,0x06, + 0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29,0x93,0x42,0x06,0x34,0xcc,0xd8,0xde, + 0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd,0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96, + 0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94,0x31,0x50,0x22,0xc5,0x0c,0x94,0x49, + 0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0xc2,0x40,0x11, + 0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91,0x94,0x49,0x49,0x03,0x16,0x70,0x73, + 0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40,0x11,0x83,0xc5,0x58,0x02,0x65,0x0c, + 0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61,0x69,0x72,0x2e,0x62,0x75,0x66,0x66, + 0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea,0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2, + 0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95,0x85,0x91,0x91,0x0a,0x4b,0x93,0x73, + 0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb,0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b, + 0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47,0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d, + 0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38,0x50,0xe4,0x60,0x21,0x16,0x62,0x11, + 0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e, + 0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33,0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1, + 0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62,0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02, + 0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d,0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c, + 0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40,0x51,0x03,0x85,0x0d,0x94,0x3c,0x18, + 0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79,0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b, + 0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33,0x95,0xd6,0x06,0xc7,0x56,0x06,0x32, + 0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44,0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81, + 0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9,0x83,0x46,0x19,0x11,0xb1,0x03,0x3b, + 0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03,0x39,0xd4,0x03,0x3b,0x94,0x83,0x1b,0x98, + 0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14,0x21,0x18,0x46,0x28,0xec,0xc0,0x0e,0xf6, + 0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e,0xee,0x40,0x0f,0x53,0x82,0x62,0xc4,0x12, + 0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e, + 0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20,0x0f,0x6e,0xc0,0x0e,0xe1,0xe0,0x0e,0xe7, + 0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0, + 0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe3,0xf0,0x0e, + 0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0,0x0b,0xef,0x00,0x0f,0xf4,0x90,0x0e,0xef, + 0xe0,0x0e,0xf3,0x30,0x65,0x50,0x18,0x67,0x84,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60, + 0x0f,0xe5,0x20,0x0f,0xf4,0x50,0x0e,0xf8,0x30,0x25,0xd8,0x03,0x00,0x79,0x18,0x00, + 0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d, + 0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c, + 0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d, + 0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d, + 0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79, + 0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc, + 0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50, + 0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30, + 0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03, + 0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07, + 0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76, + 0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98, + 0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8, + 0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21, + 0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43, + 0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f, + 0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70, + 0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0, + 0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40, + 0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41, + 0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e, + 0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07, + 0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f, + 0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d, + 0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38, + 0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88, + 0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08, + 0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50, + 0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01, + 0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43, + 0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4, + 0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e, + 0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6, + 0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0, + 0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1, + 0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f, + 0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc, + 0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19, + 0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3, + 0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87, + 0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77, + 0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00, + 0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x3e,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22, + 0x86,0x61,0x18,0xc6,0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8, + 0x95,0x40,0x19,0x14,0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00, + 0x00,0xe3,0x15,0x8c,0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9, + 0x8c,0x57,0x40,0x96,0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63, + 0x41,0x01,0x9f,0xf1,0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53, + 0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14, + 0x94,0x41,0x06,0x30,0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b, + 0x03,0x37,0x68,0x83,0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67, + 0xbc,0x62,0x0c,0xd2,0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06, + 0x26,0x04,0xf2,0xb1,0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18, + 0x50,0x50,0x6c,0x08,0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30, + 0xdb,0x10,0x3c,0xc2,0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x09,0x00,0x00, + 0x00,0x5b,0x86,0x20,0x00,0x85,0x2d,0x43,0x11,0x80,0xc2,0x96,0x41,0x09,0x40,0x61, + 0xcb,0xf0,0x04,0xa0,0xb0,0x65,0xa0,0x02,0x50,0xd8,0x32,0x60,0x01,0x28,0x6c,0x19, + 0xba,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(1.0, 1.0, 1.0, tex.sample(smp, in.uv.xy).x) * in.color; + return out; + } +*/ +static const uint8_t _sfons_fs_bytecode_metal_macos[3065] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x00,0x93,0xa3,0x32,0x70,0x27,0x04, + 0x4a,0x88,0x6f,0x1e,0x08,0x67,0xd0,0x5a,0xba,0xd2,0x72,0x48,0xca,0xe7,0x64,0x0b, + 0xdf,0x26,0xd3,0x77,0xf1,0x3d,0xde,0x93,0x55,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xbd,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xb2,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83, + 0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78, + 0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90, + 0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76, + 0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20, + 0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30, + 0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07, + 0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71, + 0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20, + 0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f, + 0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76, + 0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50, + 0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78, + 0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00, + 0x00,0x00,0x00,0x00,0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64, + 0x81,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, + 0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50, + 0x10,0x65,0x40,0x70,0x2c,0xe1,0x01,0x00,0x00,0x79,0x18,0x00,0x00,0xd1,0x00,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43, + 0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45, + 0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x84,0x67,0x61,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x51,0x58, + 0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d, + 0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0, + 0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4, + 0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd, + 0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45, + 0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d, + 0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95, + 0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69,0x72,0x2e,0x70, + 0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x14,0xea,0xec,0x86,0x48,0xcb, + 0xf0,0x58,0xcf,0xf5,0x60,0x4f,0xf6,0x40,0x4f,0xf4,0x48,0x8f,0xc6,0xa5,0x6e,0xae, + 0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x2d,0x26,0x85,0xc5,0xd8,0x1b,0xdb,0x9b,0xdc,0x10, + 0x69,0x11,0x1e,0xeb,0xe1,0x1e,0xec,0xc9,0x1e,0xe8,0x89,0x1e,0xe9,0xe9,0xb8,0x84, + 0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61, + 0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96, + 0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae, + 0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad, + 0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21,0x9e,0xef,0x01,0x83,0x65,0x58,0x84, + 0x27,0x0c,0x1e,0xe8,0x11,0x83,0x47,0x7a,0xc6,0x80,0x4b,0x58,0x9a,0x9c,0xcb,0x5c, + 0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x8f,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x0e,0x73, + 0x6d,0x70,0x43,0xa4,0xe5,0x78,0xca,0xe0,0x01,0x83,0x65,0x58,0x84,0x07,0x7a,0xcc, + 0xe0,0x91,0x9e,0x33,0x18,0x82,0x3c,0xdb,0xe3,0x3d,0x64,0xf0,0xa0,0xc1,0x10,0x03, + 0x01,0x9e,0xea,0x49,0x03,0x5e,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d, + 0x73,0x63,0x6f,0x70,0x65,0x73,0x28,0x6d,0x61,0x69,0x6e,0x30,0x29,0x43,0x88,0x87, + 0x0d,0x9e,0x35,0x20,0x16,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37, + 0xf6,0x06,0x57,0xd6,0x42,0x57,0x86,0x47,0x57,0x27,0x57,0x36,0x37,0xc4,0x78,0xdc, + 0xe0,0x61,0x83,0xa7,0x0d,0x88,0x85,0xa5,0xc9,0xb5,0x84,0xb1,0xa5,0x85,0xcd,0xb5, + 0xcc,0x8d,0xbd,0xc1,0x95,0xb5,0xcc,0x85,0xb5,0xc1,0xb1,0x95,0xc9,0xcd,0x0d,0x31, + 0x1e,0x38,0x78,0xd8,0xe0,0x79,0x83,0x21,0xc4,0xe3,0x06,0x0f,0x1c,0x8c,0x88,0xd8, + 0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1, + 0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23,0x14,0x76,0x60, + 0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87,0x29,0x41,0x31, + 0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77, + 0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60,0x87,0x70,0x70, + 0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07,0x79,0x98,0x87, + 0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79,0x70,0x83,0x71, + 0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80,0x07,0x7a,0x48, + 0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87,0x74,0x90,0x07, + 0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a,0x98,0x12,0xa8, + 0x01,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80, + 0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80, + 0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80, + 0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88, + 0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78, + 0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03, + 0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f, + 0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c, + 0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39, + 0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b, + 0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60, + 0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87, + 0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e, + 0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c, + 0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6, + 0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94, + 0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03, + 0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07, + 0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f, + 0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33, + 0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc, + 0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c, + 0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78, + 0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca, + 0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21, + 0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43, + 0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87, + 0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c, + 0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e, + 0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d, + 0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8, + 0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08, + 0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20, + 0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e, + 0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2, + 0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4, + 0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8, + 0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08, + 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10, + 0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30, + 0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78, + 0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00, + 0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00, + 0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d, + 0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x1a,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x14,0xc7,0x22, + 0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25,0x00,0x01,0xa9,0x11,0x80,0x1a,0x20,0x31, + 0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a,0x06,0x80,0xc0,0x0c,0xc0,0x08,0xc0,0x18, + 0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00,0x00,0x83,0x0c,0x0f,0x91,0x8c,0x18,0x28, + 0x43,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30,0xc8,0x70,0x04,0x8d,0x05,0x91,0x7c,0x66, + 0x1b,0x94,0x00,0xc8,0x20,0x20,0x06,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0xe0, + 0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sfons_vs_bytecode_metal_ios[3493] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xa5,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x90,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x81,0x80,0x75,0xbc,0xa9,0xd0,0xb9, + 0x4f,0xee,0x63,0x10,0x8f,0xfe,0x66,0xa5,0x3b,0x40,0xb7,0x43,0x44,0x12,0xba,0x69, + 0x7d,0xf3,0x83,0x9a,0xda,0x47,0x00,0x29,0xec,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x70,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0x19,0x03,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x70,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x6c,0x42,0x01,0x2c,0x40, + 0x35,0x84,0x43,0x3a,0xc8,0x43,0x1b,0x88,0x43,0x3d,0x98,0x83,0x39,0x94,0x83,0x3c, + 0xb4,0x81,0x3b,0xbc,0x43,0x1b,0x84,0x03,0x3b,0xa4,0x43,0x38,0xcc,0x03,0x00,0x00, + 0x00,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37, + 0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80, + 0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06, + 0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9, + 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60, + 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0, + 0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40, + 0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78, + 0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10, + 0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07, + 0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6, + 0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60, + 0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07, + 0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80, + 0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00, + 0x00,0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98, + 0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02, + 0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50,0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00, + 0x88,0x8d,0x25,0x48,0x00,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x12,0x01,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43, + 0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25, + 0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x04,0x65,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a, + 0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97, + 0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e, + 0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8, + 0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5, + 0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28,0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71, + 0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6, + 0x26,0x37,0x84,0x51,0x1e,0xc5,0x52,0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26, + 0xe7,0x02,0xf7,0x36,0x97,0x46,0x97,0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d, + 0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88,0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74, + 0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2,0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98, + 0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63,0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3, + 0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29,0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c, + 0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a, + 0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37, + 0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c,0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65, + 0x64,0x28,0x5f,0x5f,0x61,0x69,0x72,0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c, + 0x64,0x65,0x72,0x5f,0x5f,0x29,0x44,0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86, + 0x50,0x8b,0xa0,0x84,0x81,0x22,0x06,0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29, + 0x93,0x42,0x06,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd, + 0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96,0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94, + 0x31,0x50,0x22,0xc5,0x0c,0x94,0x49,0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x43,0xa8,0x65,0x50,0xc2,0x40,0x11,0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91, + 0x94,0x49,0x49,0x03,0x16,0x70,0x73,0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40, + 0x11,0x83,0xc5,0x58,0x02,0x65,0x0c,0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61, + 0x69,0x72,0x2e,0x62,0x75,0x66,0x66,0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea, + 0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2,0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95, + 0x85,0x91,0x91,0x0a,0x4b,0x93,0x73,0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb, + 0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b,0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47, + 0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d,0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38, + 0x50,0xe4,0x60,0x21,0x16,0x62,0x11,0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c, + 0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e,0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0, + 0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33, + 0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1,0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62, + 0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02,0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d, + 0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c,0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40, + 0x51,0x03,0x85,0x0d,0x94,0x3c,0x18,0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79, + 0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33, + 0x95,0xd6,0x06,0xc7,0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44, + 0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81,0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9, + 0x83,0x46,0xe1,0x15,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6, + 0x06,0x57,0x36,0x87,0xd2,0x16,0x96,0xe6,0x06,0x93,0x32,0x84,0x50,0x44,0x41,0x09, + 0x05,0x5a,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70, + 0x65,0x2d,0x61,0x72,0x67,0x28,0x34,0x29,0x43,0x0c,0x85,0x14,0x14,0x51,0x50,0x46, + 0x61,0x88,0xa0,0x90,0xc2,0x88,0x88,0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xda,0xe1, + 0x1d,0xc8,0xa1,0x1e,0xd8,0xa1,0x1c,0xdc,0xc0,0x1c,0xd8,0x21,0x1c,0xce,0x61,0x1e, + 0xa6,0x08,0xc1,0x30,0x42,0x61,0x07,0x76,0xb0,0x87,0x76,0x70,0x83,0x74,0x20,0x87, + 0x72,0x70,0x07,0x7a,0x98,0x12,0x14,0x23,0x96,0x70,0x48,0x07,0x79,0x70,0x03,0x7b, + 0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x18,0x23,0xa8,0x70,0x48, + 0x07,0x79,0x70,0x03,0x76,0x08,0x07,0x77,0x38,0x87,0x7a,0x08,0x87,0x73,0x28,0x87, + 0x5f,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x01,0x32,0x62, + 0x0a,0x87,0x74,0x90,0x07,0x37,0x18,0x87,0x77,0x68,0x07,0x78,0x48,0x07,0x76,0x28, + 0x87,0x5f,0x78,0x07,0x78,0xa0,0x87,0x74,0x78,0x07,0x77,0x98,0x87,0x29,0x83,0xc2, + 0x38,0x23,0x94,0x70,0x48,0x07,0x79,0x70,0x03,0x7b,0x28,0x07,0x79,0xa0,0x87,0x72, + 0xc0,0x87,0x29,0xc1,0x1e,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30, + 0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x42,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6, + 0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8,0x95,0x40,0x19,0x14, + 0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00,0x00,0xe3,0x15,0x8c, + 0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9,0x8c,0x57,0x40,0x96, + 0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1, + 0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53,0x99,0x10,0xc8,0xc7, + 0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14,0x94,0x41,0x06,0x30, + 0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b,0x03,0x37,0x68,0x83, + 0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67,0xbc,0x62,0x0c,0xd2, + 0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06,0x26,0x04,0xf2,0xb1, + 0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18,0x50,0x50,0x6c,0x08, + 0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30,0xdb,0x10,0x3c,0xc2, + 0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x0d,0x00,0x00,0x00,0x5b,0x8a,0x20, + 0x00,0x85,0xa3,0x14,0xb6,0x14,0x45,0x00,0x0a,0x47,0x29,0x6c,0x29,0x94,0x00,0x14, + 0x8e,0x52,0xd8,0x52,0x3c,0x01,0x28,0x1c,0xa5,0xb0,0xa5,0xa0,0x02,0x50,0x38,0x4a, + 0x61,0x4b,0x81,0x05,0xa0,0x70,0x94,0xc2,0x96,0xa2,0x0b,0x40,0xe1,0x28,0x05,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(1.0, 1.0, 1.0, tex.sample(smp, in.uv.xy).x) * in.color; + return out; + } +*/ +static const uint8_t _sfons_fs_bytecode_metal_ios[3049] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xe9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x10,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x1c,0x34,0xc1,0x68,0x6e,0xbc,0x1a, + 0x30,0x92,0x74,0x28,0x57,0x7d,0x71,0xda,0x98,0x7b,0x20,0xf9,0x6e,0x40,0x73,0x89, + 0x90,0x99,0xa5,0x20,0xbe,0x88,0x63,0xc5,0x97,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xf4,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xba,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xa8,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51, + 0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78, + 0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60, + 0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07, + 0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, + 0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30, + 0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0, + 0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, + 0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, + 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90, + 0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06, + 0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, + 0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0, + 0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07, + 0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72, + 0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10, + 0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73, + 0x20,0x07,0x43,0x18,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03, + 0x04,0x80,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, + 0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0x41,0x02,0x00, + 0x00,0x79,0x18,0x00,0x00,0xd0,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x21,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1, + 0x95,0x0d,0x11,0x9e,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc, + 0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9, + 0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89, + 0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac, + 0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a, + 0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32, + 0x39,0x3e,0x61,0x69,0x72,0x2e,0x70,0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76, + 0x65,0x14,0xea,0xec,0x86,0x48,0xcb,0xf0,0x58,0xcf,0xf5,0x60,0x4f,0xf6,0x40,0x4f, + 0xf4,0x48,0x8f,0xc6,0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x2d,0x26,0x85, + 0xc5,0xd8,0x1b,0xdb,0x9b,0xdc,0x10,0x69,0x11,0x1e,0xeb,0xe1,0x1e,0xec,0xc9,0x1e, + 0xe8,0x89,0x1e,0xe9,0xe9,0xb8,0x84,0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9, + 0x95,0x51,0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a, + 0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46, + 0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e, + 0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21, + 0x9e,0xef,0x01,0x83,0x65,0x58,0x84,0x27,0x0c,0x1e,0xe8,0x11,0x83,0x47,0x7a,0xc6, + 0x80,0x4b,0x58,0x9a,0x9c,0xcb,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x8f,0xb9,0xb0, + 0x36,0x38,0xb6,0x32,0x39,0x0e,0x73,0x6d,0x70,0x43,0xa4,0xe5,0x78,0xca,0xe0,0x01, + 0x83,0x65,0x58,0x84,0x07,0x7a,0xcc,0xe0,0x91,0x9e,0x33,0x18,0x82,0x3c,0xdb,0xe3, + 0x3d,0x64,0xf0,0xa0,0xc1,0x10,0x03,0x01,0x9e,0xea,0x49,0x03,0x5e,0x61,0x69,0x72, + 0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70,0x65,0x73,0x28,0x6d,0x61, + 0x69,0x6e,0x30,0x29,0x43,0x88,0x87,0x0d,0x9e,0x35,0x20,0x16,0x96,0x26,0xd7,0x12, + 0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6,0x06,0x57,0xd6,0x42,0x57,0x86,0x47,0x57, + 0x27,0x57,0x36,0x37,0xc4,0x78,0xdc,0xe0,0x61,0x83,0xa7,0x0d,0x88,0x85,0xa5,0xc9, + 0xb5,0x84,0xb1,0xa5,0x85,0xcd,0xb5,0xcc,0x8d,0xbd,0xc1,0x95,0xb5,0xcc,0x85,0xb5, + 0xc1,0xb1,0x95,0xc9,0xcd,0x0d,0x31,0x1e,0x38,0x78,0xd8,0xe0,0x79,0x83,0x21,0xc4, + 0xe3,0x06,0x0f,0x1c,0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde, + 0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61, + 0x8a,0x10,0x0c,0x23,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28, + 0x07,0x77,0xa0,0x87,0x29,0x41,0x31,0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87, + 0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74, + 0x90,0x07,0x37,0x60,0x87,0x70,0x70,0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8, + 0x05,0x7b,0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6, + 0x70,0x48,0x07,0x79,0x70,0x83,0x71,0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72, + 0xf8,0x85,0x77,0x80,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c, + 0x33,0x82,0x09,0x87,0x74,0x90,0x07,0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87, + 0x72,0x70,0x07,0x7a,0x98,0x12,0xa8,0x01,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01, + 0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e, + 0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00, + 0x00,0x1a,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0b,0x00,0x00, + 0x00,0x14,0xc7,0x22,0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25,0x00,0x01,0xa9,0x11, + 0x80,0x1a,0x20,0x31,0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a,0x06,0x80,0xc0,0x0c, + 0xc0,0x08,0xc0,0x18,0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00,0x00,0x83,0x0c,0x0f, + 0x91,0x8c,0x18,0x28,0x43,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30,0xc8,0x70,0x04,0x8d, + 0x05,0x91,0x7c,0x66,0x1b,0x94,0x00,0xc8,0x20,0x20,0x06,0x00,0x00,0x02,0x00,0x00, + 0x00,0x5b,0x86,0xe0,0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sfons_vs_source_metal_sim[756] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a, + 0x65,0x20,0x5b,0x5b,0x70,0x6f,0x69,0x6e,0x74,0x5f,0x73,0x69,0x7a,0x65,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75, + 0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72, + 0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a, + 0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69, + 0x7a,0x65,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e, + 0x74,0x6d,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74, + 0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = float4(1.0, 1.0, 1.0, tex.sample(smp, in.uv.xy).x) * in.color; + return out; + } +*/ +static const uint8_t _sfons_fs_source_metal_sim[464] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65, + 0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65, + 0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d, + 0x70,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20, + 0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +/* + cbuffer vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + row_major float4x4 _19_tm : packoffset(c4); + }; + + + static float4 gl_Position; + static float gl_PointSize; + static float4 position; + static float psize; + static float4 uv; + static float2 texcoord0; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float2 texcoord0 : TEXCOORD1; + float4 color0 : TEXCOORD2; + float psize : TEXCOORD3; + }; + + struct SPIRV_Cross_Output + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + gl_PointSize = psize; + uv = mul(float4(texcoord0, 0.0f, 1.0f), _19_tm); + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + psize = stage_input.psize; + texcoord0 = stage_input.texcoord0; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.uv = uv; + stage_output.color = color; + return stage_output; + } +*/ +static const uint8_t _sfons_vs_bytecode_hlsl4[1032] = { + 0x44,0x58,0x42,0x43,0x74,0x7f,0x01,0xd9,0xf4,0xd5,0xed,0x1d,0x74,0xc1,0x30,0x27, + 0xd8,0xe9,0x9d,0x50,0x01,0x00,0x00,0x00,0x08,0x04,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x02,0x00,0x00, + 0x8c,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, + 0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f, + 0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, + 0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, + 0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, + 0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x74,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x03,0x00,0x00,0x68,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, + 0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, + 0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab, + 0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03, + 0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, + 0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04, + 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02, + 0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05, + 0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00, + 0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54, + 0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + Texture2D tex : register(t0); + SamplerState smp : register(s0); + + static float4 frag_color; + static float4 uv; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = float4(1.0f, 1.0f, 1.0f, tex.Sample(smp, uv.xy).x) * color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + uv = stage_input.uv; + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +static const uint8_t _sfons_fs_bytecode_hlsl4[628] = { + 0x44,0x58,0x42,0x43,0xb6,0x66,0xf0,0xfc,0x09,0x54,0x2a,0x35,0x84,0x1d,0x27,0xd2, + 0xff,0xb3,0x2e,0xdb,0x01,0x00,0x00,0x00,0x74,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x48,0x01,0x00,0x00, + 0xf8,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0x64,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c, + 0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c, + 0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00, + 0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54, + 0x61,0x72,0x67,0x65,0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0xa8,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00, + 0x55,0x55,0x00,0x00,0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x62,0x10,0x00,0x03,0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00, + 0x45,0x00,0x00,0x09,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x96,0x73,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0x12,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x01,0x40,0x00,0x00,0x00,0x00,0x80,0x3f,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00, + 0x00,0x00,0x00,0x00,0x06,0x0c,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00, + 0x01,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +/* + diagnostic(off, derivative_uniformity); + + struct vs_params { + /_ @offset(0) _/ + mvp : mat4x4f, + /_ @offset(64) _/ + tm : mat4x4f, + } + + @group(0) @binding(0) var x_19 : vs_params; + + var position_1 : vec4f; + + var uv : vec4f; + + var texcoord0 : vec2f; + + var color : vec4f; + + var color0 : vec4f; + + var psize : f32; + + var gl_Position : vec4f; + + fn main_1() { + let x_22 : mat4x4f = x_19.mvp; + let x_25 : vec4f = position_1; + gl_Position = (x_22 * x_25); + let x_32 : mat4x4f = x_19.tm; + let x_36 : vec2f = texcoord0; + uv = (x_32 * vec4f(x_36.x, x_36.y, 0.0f, 1.0f)); + let x_45 : vec4f = color0; + color = x_45; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + uv_1 : vec4f, + @location(1) + color_1 : vec4f, + } + + @vertex + fn main(@location(0) position_1_param : vec4f, @location(1) texcoord0_param : vec2f, @location(2) color0_param : vec4f, @location(3) psize_param : f32) -> main_out { + position_1 = position_1_param; + texcoord0 = texcoord0_param; + color0 = color0_param; + psize = psize_param; + main_1(); + return main_out(gl_Position, uv, color); + } +*/ +static const uint8_t _sfons_vs_source_wgsl[1162] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a, + 0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20, + 0x20,0x6d,0x76,0x70,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78,0x34,0x66,0x2c,0x0a, + 0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x36,0x34,0x29, + 0x20,0x2a,0x2f,0x0a,0x20,0x20,0x74,0x6d,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29, + 0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x30,0x29,0x20,0x76,0x61,0x72, + 0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a, + 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x76,0x61,0x72, + 0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76, + 0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65, + 0x3e,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x0a, + 0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x32,0x20,0x3a,0x20,0x6d,0x61, + 0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x35,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f, + 0x31,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x32,0x20,0x2a,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x6d, + 0x61,0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x74,0x6d, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x36,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x3b,0x0a,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x28,0x78,0x5f,0x33,0x32,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x33,0x36,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x33,0x36,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x35,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f,0x34,0x35, + 0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b, + 0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74,0x69,0x6e,0x28,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20,0x75,0x76, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a, + 0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x28,0x31,0x29,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x32,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c, + 0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x33,0x29,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x66,0x33,0x32,0x29, + 0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x20,0x3d,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x70,0x73,0x69,0x7a,0x65,0x20, + 0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x75,0x76,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var frag_color : vec4f; + + @group(1) @binding(64) var tex : texture_2d; + + @group(1) @binding(80) var smp : sampler; + + var uv : vec4f; + + var color : vec4f; + + fn main_1() { + let x_24 : vec4f = uv; + let x_26 : vec4f = textureSample(tex, smp, vec2f(x_24.x, x_24.y)); + let x_32 : vec4f = color; + frag_color = (vec4f(1.0f, 1.0f, 1.0f, x_26.x) * x_32); + return; + } + + struct main_out { + @location(0) + frag_color_1 : vec4f, + } + + @fragment + fn main(@location(0) uv_param : vec4f, @location(1) color_param : vec4f) -> main_out { + uv = uv_param; + color = color_param; + main_1(); + return main_out(frag_color); + } +*/ +static const uint8_t _sfons_fs_source_wgsl[674] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75, + 0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34, + 0x29,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x20,0x3a,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67, + 0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67, + 0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x73, + 0x6d,0x70,0x2c,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x32,0x34,0x2e,0x78, + 0x2c,0x20,0x78,0x5f,0x32,0x34,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x76,0x65,0x63,0x34,0x66,0x28,0x31,0x2e, + 0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20, + 0x78,0x5f,0x32,0x36,0x2e,0x78,0x29,0x20,0x2a,0x20,0x78,0x5f,0x33,0x32,0x29,0x3b, + 0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a, + 0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20, + 0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x66,0x72,0x61,0x67,0x6d, + 0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f,0x63, + 0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x29,0x20,0x2d,0x3e,0x20, + 0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x75,0x76,0x20, + 0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74, + 0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sfons_vs_source_dummy = ""; +static const char* _sfons_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +typedef struct _sfons_t { + sfons_desc_t desc; + sg_shader shd; + sgl_pipeline pip; + sg_image img; + sg_sampler smp; + int cur_width, cur_height; + bool img_dirty; +} _sfons_t; + +static void _sfons_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _sfons_malloc(const sfons_allocator_t* allocator, size_t size) { + SOKOL_ASSERT(allocator && (size > 0)); + void* ptr; + if (allocator->alloc_fn) { + ptr = allocator->alloc_fn(size, allocator->user_data); + } else { + ptr = malloc(size); + } + SOKOL_ASSERT(ptr); + return ptr; +} + +static void* _sfons_malloc_clear(const sfons_allocator_t* allocator, size_t size) { + void* ptr = _sfons_malloc(allocator, size); + _sfons_clear(ptr, size); + return ptr; +} + +static void _sfons_free(const sfons_allocator_t* allocator, void* ptr) { + SOKOL_ASSERT(allocator); + if (allocator->free_fn) { + allocator->free_fn(ptr, allocator->user_data); + } else { + free(ptr); + } +} + +static int _sfons_render_create(void* user_ptr, int width, int height) { + SOKOL_ASSERT(user_ptr && (width > 8) && (height > 8)); + _sfons_t* sfons = (_sfons_t*) user_ptr; + + // sokol-gl compatible shader which treats RED channel as alpha + if (sfons->shd.id == SG_INVALID_ID) { + sg_shader_desc shd_desc; + _sfons_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].glsl_name = "position"; + shd_desc.attrs[1].glsl_name = "texcoord0"; + shd_desc.attrs[2].glsl_name = "color0"; + shd_desc.attrs[3].glsl_name = "psize"; + shd_desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[0].hlsl_sem_index = 0; + shd_desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[1].hlsl_sem_index = 1; + shd_desc.attrs[2].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[2].hlsl_sem_index = 2; + shd_desc.attrs[3].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[3].hlsl_sem_index = 3; + shd_desc.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX; + shd_desc.uniform_blocks[0].size = 128; + shd_desc.uniform_blocks[0].hlsl_register_b_n = 0; + shd_desc.uniform_blocks[0].msl_buffer_n = 0; + shd_desc.uniform_blocks[0].wgsl_group0_binding_n = 0; + shd_desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "vs_params"; + shd_desc.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + shd_desc.uniform_blocks[0].glsl_uniforms[0].array_count = 8; + shd_desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.images[0].hlsl_register_t_n = 0; + shd_desc.images[0].msl_texture_n = 0; + shd_desc.images[0].wgsl_group1_binding_n = 64; + shd_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.samplers[0].hlsl_register_s_n = 0; + shd_desc.samplers[0].msl_sampler_n = 0; + shd_desc.samplers[0].wgsl_group1_binding_n = 80; + shd_desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.image_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.image_sampler_pairs[0].image_slot = 0; + shd_desc.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.label = "sokol-fontstash-shader"; + #if defined(SOKOL_GLCORE) + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_glsl410; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_glsl410; + #elif defined(SOKOL_GLES3) + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_glsl300es; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vertex_func.entry = "main0"; + shd_desc.fragment_func.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_macos); + shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_ios); + shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_ios); + break; + default: + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_metal_sim; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vertex_func.bytecode = SG_RANGE(_sfons_vs_bytecode_hlsl4); + shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vertex_func.source = (const char*)_sfons_vs_source_wgsl; + shd_desc.fragment_func.source = (const char*)_sfons_fs_source_wgsl; + #else + shd_desc.vertex_func.source = _sfons_vs_source_dummy; + shd_desc.fragment_func.source = _sfons_fs_source_dummy; + #endif + shd_desc.label = "sfons-shader"; + sfons->shd = sg_make_shader(&shd_desc); + } + + // sokol-gl pipeline object + if (sfons->pip.id == SG_INVALID_ID) { + sg_pipeline_desc pip_desc; + _sfons_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.shader = sfons->shd; + pip_desc.colors[0].blend.enabled = true; + pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + sfons->pip = sgl_make_pipeline(&pip_desc); + } + + // a sampler object + if (sfons->smp.id == SG_INVALID_ID) { + sg_sampler_desc smp_desc; + _sfons_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.min_filter = SG_FILTER_LINEAR; + smp_desc.mag_filter = SG_FILTER_LINEAR; + sfons->smp = sg_make_sampler(&smp_desc); + } + + // create or re-create font atlas texture + if (sfons->img.id != SG_INVALID_ID) { + sg_destroy_image(sfons->img); + sfons->img.id = SG_INVALID_ID; + } + sfons->cur_width = width; + sfons->cur_height = height; + + SOKOL_ASSERT(sfons->img.id == SG_INVALID_ID); + sg_image_desc img_desc; + _sfons_clear(&img_desc, sizeof(img_desc)); + img_desc.width = sfons->cur_width; + img_desc.height = sfons->cur_height; + img_desc.usage = SG_USAGE_DYNAMIC; + img_desc.pixel_format = SG_PIXELFORMAT_R8; + sfons->img = sg_make_image(&img_desc); + return 1; +} + +static int _sfons_render_resize(void* user_ptr, int width, int height) { + return _sfons_render_create(user_ptr, width, height); +} + +static void _sfons_render_update(void* user_ptr, int* rect, const unsigned char* data) { + SOKOL_ASSERT(user_ptr && rect && data); + _SOKOL_UNUSED(rect); + _SOKOL_UNUSED(data); + _sfons_t* sfons = (_sfons_t*) user_ptr; + sfons->img_dirty = true; +} + +static void _sfons_render_draw(void* user_ptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) { + SOKOL_ASSERT(user_ptr && verts && tcoords && colors && (nverts > 0)); + _sfons_t* sfons = (_sfons_t*) user_ptr; + sgl_enable_texture(); + sgl_texture(sfons->img, sfons->smp); + sgl_push_pipeline(); + sgl_load_pipeline(sfons->pip); + sgl_begin_triangles(); + for (int i = 0; i < nverts; i++) { + sgl_v2f_t2f_c1i(verts[2*i+0], verts[2*i+1], tcoords[2*i+0], tcoords[2*i+1], colors[i]); + } + sgl_end(); + sgl_pop_pipeline(); + sgl_disable_texture(); +} + +static void _sfons_render_delete(void* user_ptr) { + SOKOL_ASSERT(user_ptr); + _sfons_t* sfons = (_sfons_t*) user_ptr; + if (sfons->img.id != SG_INVALID_ID) { + sg_destroy_image(sfons->img); + sfons->img.id = SG_INVALID_ID; + } + if (sfons->smp.id != SG_INVALID_ID) { + sg_destroy_sampler(sfons->smp); + sfons->smp.id = SG_INVALID_ID; + } + if (sfons->pip.id != SG_INVALID_ID) { + sgl_destroy_pipeline(sfons->pip); + sfons->pip.id = SG_INVALID_ID; + } + if (sfons->shd.id != SG_INVALID_ID) { + sg_destroy_shader(sfons->shd); + sfons->shd.id = SG_INVALID_ID; + } +} + +#define _sfons_def(val, def) (((val) == 0) ? (def) : (val)) + +static sfons_desc_t _sfons_desc_defaults(const sfons_desc_t* desc) { + SOKOL_ASSERT(desc); + sfons_desc_t res = *desc; + res.width = _sfons_def(res.width, 512); + res.height = _sfons_def(res.height, 512); + return res; +} + +SOKOL_API_IMPL FONScontext* sfons_create(const sfons_desc_t* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + _sfons_t* sfons = (_sfons_t*) _sfons_malloc_clear(&desc->allocator, sizeof(_sfons_t)); + sfons->desc = _sfons_desc_defaults(desc); + FONSparams params; + _sfons_clear(¶ms, sizeof(params)); + params.width = sfons->desc.width; + params.height = sfons->desc.height; + params.flags = FONS_ZERO_TOPLEFT; + params.renderCreate = _sfons_render_create; + params.renderResize = _sfons_render_resize; + params.renderUpdate = _sfons_render_update; + params.renderDraw = _sfons_render_draw; + params.renderDelete = _sfons_render_delete; + params.userPtr = sfons; + return fonsCreateInternal(¶ms); +} + +SOKOL_API_IMPL void sfons_destroy(FONScontext* ctx) { + SOKOL_ASSERT(ctx); + _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; + fonsDeleteInternal(ctx); + const sfons_allocator_t allocator = sfons->desc.allocator; + _sfons_free(&allocator, sfons); +} + +SOKOL_API_IMPL void sfons_flush(FONScontext* ctx) { + SOKOL_ASSERT(ctx && ctx->params.userPtr); + _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; + if (sfons->img_dirty) { + sfons->img_dirty = false; + sg_image_data data; + _sfons_clear(&data, sizeof(data)); + data.subimage[0][0].ptr = ctx->texData; + data.subimage[0][0].size = (size_t) (sfons->cur_width * sfons->cur_height); + sg_update_image(sfons->img, &data); + } +} + +SOKOL_API_IMPL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return ((uint32_t)r) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); +} + +#endif // SOKOL_FONTSTASH_IMPL diff --git a/modules/sokol-jai/sokol/c/sokol_gfx.c b/modules/sokol-jai/sokol/c/sokol_gfx.c new file mode 100644 index 0000000..984b8b4 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_gfx.c @@ -0,0 +1,5 @@ +#if defined(IMPL) +#define SOKOL_GFX_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_gfx.h" diff --git a/modules/sokol-jai/sokol/c/sokol_gfx.h b/modules/sokol-jai/sokol/c/sokol_gfx.h new file mode 100644 index 0000000..1e63aa3 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_gfx.h @@ -0,0 +1,21518 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GFX_IMPL) +#define SOKOL_GFX_IMPL +#endif +#ifndef SOKOL_GFX_INCLUDED +/* + sokol_gfx.h -- simple 3D API wrapper + + Project URL: https://github.com/floooh/sokol + + Example code: https://github.com/floooh/sokol-samples + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GFX_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the rendering + backend: + #define SOKOL_GLCORE + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_DUMMY_BACKEND + + I.e. for the desktop GL it should look like this: + + #include ... + #include ... + #define SOKOL_IMPL + #define SOKOL_GLCORE + #include "sokol_gfx.h" + + The dummy backend replaces the platform-specific backend code with empty + stub functions. This is useful for writing tests that need to run on the + command line. + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) + SOKOL_EXTERNAL_GL_LOADER - indicates that you're using your own GL loader, in this case + sokol_gfx.h will not include any platform GL headers and disable + the integrated Win32 GL loader + + If sokol_gfx.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GFX_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + If you want to compile without deprecated structs and functions, + define: + + SOKOL_NO_DEPRECATED + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + sokol_gfx DOES NOT: + =================== + - create a window, swapchain or the 3D-API context/device, you must do this + before sokol_gfx is initialized, and pass any required information + (like 3D device pointers) to the sokol_gfx initialization call + + - present the rendered frame, how this is done exactly usually depends + on how the window and 3D-API context/device was created + + - provide a unified shader language, instead 3D-API-specific shader + source-code or shader-bytecode must be provided (for the "official" + offline shader cross-compiler / code-generator, see here: + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + + + STEP BY STEP + ============ + --- to initialize sokol_gfx, after creating a window and a 3D-API + context/device, call: + + sg_setup(const sg_desc*) + + Depending on the selected 3D backend, sokol-gfx requires some + information about its runtime environment, like a GPU device pointer, + default swapchain pixel formats and so on. If you are using sokol_app.h + for the window system glue, you can use a helper function provided in + the sokol_glue.h header: + + #include "sokol_gfx.h" + #include "sokol_app.h" + #include "sokol_glue.h" + //... + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + }); + + To get any logging output for errors and from the validation layer, you + need to provide a logging callback. Easiest way is through sokol_log.h: + + #include "sokol_log.h" + //... + sg_setup(&(sg_desc){ + //... + .logger.func = slog_func, + }); + + --- create resource objects (at least buffers, shaders and pipelines, + and optionally images, samplers and render-pass-attachments): + + sg_buffer sg_make_buffer(const sg_buffer_desc*) + sg_image sg_make_image(const sg_image_desc*) + sg_sampler sg_make_sampler(const sg_sampler_desc*) + sg_shader sg_make_shader(const sg_shader_desc*) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc*) + sg_attachments sg_make_attachments(const sg_attachments_desc*) + + --- start a render- or compute-pass: + + sg_begin_pass(const sg_pass* pass); + + Typically, render passes render into an externally provided swapchain which + presents the rendering result on the display. Such a 'swapchain pass' + is started like this: + + sg_begin_pass(&(sg_pass){ .action = { ... }, .swapchain = sglue_swapchain() }) + + ...where .action is an sg_pass_action struct containing actions to be performed + at the start and end of a render pass (such as clearing the render surfaces to + a specific color), and .swapchain is an sg_swapchain struct with all the required + information to render into the swapchain's surfaces. + + To start an 'offscreen render pass' into sokol-gfx image objects, an sg_attachment + object handle is required instead of an sg_swapchain struct. An offscreen + pass is started like this (assuming attachments is an sg_attachments handle): + + sg_begin_pass(&(sg_pass){ .action = { ... }, .attachments = attachments }); + + To start a compute-pass, just set the .compute item to true: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + --- set the pipeline state for the next draw call with: + + sg_apply_pipeline(sg_pipeline pip) + + --- fill an sg_bindings struct with the resource bindings for the next + draw- or dispatch-call (0..N vertex buffers, 0 or 1 index buffer, 0..N images, + samplers and storage-buffers), and call: + + sg_apply_bindings(const sg_bindings* bindings) + + to update the resource bindings. Note that in a compute pass, no vertex- + or index-buffer bindings are allowed and will be rejected by the validation + layer. + + --- optionally update shader uniform data with: + + sg_apply_uniforms(int ub_slot, const sg_range* data) + + Read the section 'UNIFORM DATA LAYOUT' to learn about the expected memory layout + of the uniform data passed into sg_apply_uniforms(). + + --- kick off a draw call with: + + sg_draw(int base_element, int num_elements, int num_instances) + + The sg_draw() function unifies all the different ways to render primitives + in a single call (indexed vs non-indexed rendering, and instanced vs non-instanced + rendering). In case of indexed rendering, base_element and num_element specify + indices in the currently bound index buffer. In case of non-indexed rendering + base_element and num_elements specify vertices in the currently bound + vertex-buffer(s). To perform instanced rendering, the rendering pipeline + must be setup for instancing (see sg_pipeline_desc below), a separate vertex buffer + containing per-instance data must be bound, and the num_instances parameter + must be > 1. + + --- ...or kick of a dispatch call to invoke a compute shader workload: + + sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) + + The dispatch args define the number of 'compute workgroups' processed + by the currently applied compute shader. + + --- finish the current pass with: + + sg_end_pass() + + --- when done with the current frame, call + + sg_commit() + + --- at the end of your program, shutdown sokol_gfx with: + + sg_shutdown() + + --- if you need to destroy resources before sg_shutdown(), call: + + sg_destroy_buffer(sg_buffer buf) + sg_destroy_image(sg_image img) + sg_destroy_sampler(sg_sampler smp) + sg_destroy_shader(sg_shader shd) + sg_destroy_pipeline(sg_pipeline pip) + sg_destroy_attachments(sg_attachments atts) + + --- to set a new viewport rectangle, call: + + sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) + + ...or if you want to specify the viewport rectangle with float values: + + sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) + + --- to set a new scissor rect, call: + + sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) + + ...or with float values: + + sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) + + Both sg_apply_viewport() and sg_apply_scissor_rect() must be called + inside a rendering pass (e.g. not in a compute pass, or outside a pass) + + Note that sg_begin_default_pass() and sg_begin_pass() will reset both the + viewport and scissor rectangles to cover the entire framebuffer. + + --- to update (overwrite) the content of buffer and image resources, call: + + sg_update_buffer(sg_buffer buf, const sg_range* data) + sg_update_image(sg_image img, const sg_image_data* data) + + Buffers and images to be updated must have been created with + SG_USAGE_DYNAMIC or SG_USAGE_STREAM. + + Only one update per frame is allowed for buffer and image resources when + using the sg_update_*() functions. The rationale is to have a simple + protection from the CPU scribbling over data the GPU is currently + using, or the CPU having to wait for the GPU + + Buffer and image updates can be partial, as long as a rendering + operation only references the valid (updated) data in the + buffer or image. + + --- to append a chunk of data to a buffer resource, call: + + int sg_append_buffer(sg_buffer buf, const sg_range* data) + + The difference to sg_update_buffer() is that sg_append_buffer() + can be called multiple times per frame to append new data to the + buffer piece by piece, optionally interleaved with draw calls referencing + the previously written data. + + sg_append_buffer() returns a byte offset to the start of the + written data, this offset can be assigned to + sg_bindings.vertex_buffer_offsets[n] or + sg_bindings.index_buffer_offset + + Code example: + + for (...) { + const void* data = ...; + const int num_bytes = ...; + int offset = sg_append_buffer(buf, &(sg_range) { .ptr=data, .size=num_bytes }); + bindings.vertex_buffer_offsets[0] = offset; + sg_apply_pipeline(pip); + sg_apply_bindings(&bindings); + sg_apply_uniforms(...); + sg_draw(...); + } + + A buffer to be used with sg_append_buffer() must have been created + with SG_USAGE_DYNAMIC or SG_USAGE_STREAM. + + If the application appends more data to the buffer then fits into + the buffer, the buffer will go into the "overflow" state for the + rest of the frame. + + Any draw calls attempting to render an overflown buffer will be + silently dropped (in debug mode this will also result in a + validation error). + + You can also check manually if a buffer is in overflow-state by calling + + bool sg_query_buffer_overflow(sg_buffer buf) + + You can manually check to see if an overflow would occur before adding + any data to a buffer by calling + + bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size) + + NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of + data will be 4-byte aligned in the destination buffer. This means + that there will be gaps in index buffers containing 16-bit indices + when the number of indices in a call to sg_append_buffer() is + odd. This isn't a problem when each call to sg_append_buffer() + is associated with one draw call, but will be problematic when + a single indexed draw call spans several appended chunks of indices. + + --- to check at runtime for optional features, limits and pixelformat support, + call: + + sg_features sg_query_features() + sg_limits sg_query_limits() + sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) + + --- if you need to call into the underlying 3D-API directly, you must call: + + sg_reset_state_cache() + + ...before calling sokol_gfx functions again + + --- you can inspect the original sg_desc structure handed to sg_setup() + by calling sg_query_desc(). This will return an sg_desc struct with + the default values patched in instead of any zero-initialized values + + --- you can get a desc struct matching the creation attributes of a + specific resource object via: + + sg_buffer_desc sg_query_buffer_desc(sg_buffer buf) + sg_image_desc sg_query_image_desc(sg_image img) + sg_sampler_desc sg_query_sampler_desc(sg_sampler smp) + sg_shader_desc sq_query_shader_desc(sg_shader shd) + sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip) + sg_attachments_desc sg_query_attachments_desc(sg_attachments atts) + + ...but NOTE that the returned desc structs may be incomplete, only + creation attributes that are kept around internally after resource + creation will be filled in, and in some cases (like shaders) that's + very little. Any missing attributes will be set to zero. The returned + desc structs might still be useful as partial blueprint for creating + similar resources if filled up with the missing attributes. + + Calling the query-desc functions on an invalid resource will return + completely zeroed structs (it makes sense to check the resource state + with sg_query_*_state() first) + + --- you can query the default resource creation parameters through the functions + + sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) + sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) + sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc) + sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) + sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) + sg_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc* desc) + + These functions take a pointer to a desc structure which may contain + zero-initialized items for default values. These zero-init values + will be replaced with their concrete values in the returned desc + struct. + + --- you can inspect various internal resource runtime values via: + + sg_buffer_info sg_query_buffer_info(sg_buffer buf) + sg_image_info sg_query_image_info(sg_image img) + sg_sampler_info sg_query_sampler_info(sg_sampler smp) + sg_shader_info sg_query_shader_info(sg_shader shd) + sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) + sg_attachments_info sg_query_attachments_info(sg_attachments atts) + + ...please note that the returned info-structs are tied quite closely + to sokol_gfx.h internals, and may change more often than other + public API functions and structs. + + --- you can query frame stats and control stats collection via: + + sg_query_frame_stats() + sg_enable_frame_stats() + sg_disable_frame_stats() + sg_frame_stats_enabled() + + --- you can ask at runtime what backend sokol_gfx.h has been compiled for: + + sg_backend sg_query_backend(void) + + --- call the following helper functions to compute the number of + bytes in a texture row or surface for a specific pixel format. + These functions might be helpful when preparing image data for consumption + by sg_make_image() or sg_update_image(): + + int sg_query_row_pitch(sg_pixel_format fmt, int width, int int row_align_bytes); + int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes); + + Width and height are generally in number pixels, but note that 'row' has different meaning + for uncompressed vs compressed pixel formats: for uncompressed formats, a row is identical + with a single line if pixels, while in compressed formats, one row is a line of *compression blocks*. + + This is why calling sg_query_surface_pitch() for a compressed pixel format and height + N, N+1, N+2, ... may return the same result. + + The row_align_bytes parammeter is for added flexibility. For image data that goes into + the sg_make_image() or sg_update_image() this should generally be 1, because these + functions take tightly packed image data as input no matter what alignment restrictions + exist in the backend 3D APIs. + + ON INITIALIZATION: + ================== + When calling sg_setup(), a pointer to an sg_desc struct must be provided + which contains initialization options. These options provide two types + of information to sokol-gfx: + + (1) upper bounds and limits needed to allocate various internal + data structures: + - the max number of resources of each type that can + be alive at the same time, this is used for allocating + internal pools + - the max overall size of uniform data that can be + updated per frame, including a worst-case alignment + per uniform update (this worst-case alignment is 256 bytes) + - the max size of all dynamic resource updates (sg_update_buffer, + sg_append_buffer and sg_update_image) per frame + - the max number of compute-dispatch calls in a compute pass + Not all of those limit values are used by all backends, but it is + good practice to provide them none-the-less. + + (2) 3D backend "environment information" in a nested sg_environment struct: + - pointers to backend-specific context- or device-objects (for instance + the D3D11, WebGPU or Metal device objects) + - defaults for external swapchain pixel formats and sample counts, + these will be used as default values in image and pipeline objects, + and the sg_swapchain struct passed into sg_begin_pass() + Usually you provide a complete sg_environment struct through + a helper function, as an example look at the sglue_environment() + function in the sokol_glue.h header. + + See the documentation block of the sg_desc struct below for more information. + + + ON RENDER PASSES + ================ + Relevant samples: + - https://floooh.github.io/sokol-html5/offscreen-sapp.html + - https://floooh.github.io/sokol-html5/offscreen-msaa-sapp.html + - https://floooh.github.io/sokol-html5/mrt-sapp.html + - https://floooh.github.io/sokol-html5/mrt-pixelformats-sapp.html + + A render pass groups rendering commands into a set of render target images + (called 'pass attachments'). Render target images can be used in subsequent + passes as textures (it is invalid to use the same image both as render target + and as texture in the same pass). + + The following sokol-gfx functions must only be called inside a render-pass: + + sg_apply_viewport[f] + sg_apply_scissor_rect[f] + sg_draw + + The folling function may be called inside a render- or compute-pass, but + not outside a pass: + + sg_apply_pipeline + sg_apply_bindings + sg_apply_uniforms + + A frame must have at least one 'swapchain render pass' which renders into an + externally provided swapchain provided as an sg_swapchain struct to the + sg_begin_pass() function. If you use sokol_gfx.h together with sokol_app.h, + just call the sglue_swapchain() helper function in sokol_glue.h to + provide the swapchain information. Otherwise the following information + must be provided: + + - the color pixel-format of the swapchain's render surface + - an optional depth/stencil pixel format if the swapchain + has a depth/stencil buffer + - an optional sample-count for MSAA rendering + - NOTE: the above three values can be zero-initialized, in that + case the defaults from the sg_environment struct will be used that + had been passed to the sg_setup() function. + - a number of backend specific objects: + - GL/GLES3: just a GL framebuffer handle + - D3D11: + - an ID3D11RenderTargetView for the rendering surface + - if MSAA is used, an ID3D11RenderTargetView as + MSAA resolve-target + - an optional ID3D11DepthStencilView for the + depth/stencil buffer + - WebGPU + - a WGPUTextureView object for the rendering surface + - if MSAA is used, a WGPUTextureView object as MSAA resolve target + - an optional WGPUTextureView for the + - Metal (NOTE that the roles of provided surfaces is slightly + different in Metal than in D3D11 or WebGPU, notably, the + CAMetalDrawable is either rendered to directly, or serves + as MSAA resolve target): + - a CAMetalDrawable object which is either rendered + into directly, or in case of MSAA rendering, serves + as MSAA-resolve-target + - if MSAA is used, an multisampled MTLTexture where + rendering goes into + - an optional MTLTexture for the depth/stencil buffer + + It's recommended that you create a helper function which returns an + initialized sg_swapchain struct by value. This can then be directly plugged + into the sg_begin_pass function like this: + + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); + + As an example for such a helper function check out the function sglue_swapchain() + in the sokol_glue.h header. + + For offscreen render passes, the render target images used in a render pass + are baked into an immutable sg_attachments object. + + For a simple offscreen scenario with one color-, one depth-stencil-render + target and without multisampling, creating an attachment object looks like this: + + First create two render target images, one with a color pixel format, + and one with the depth- or depth-stencil pixel format. Both images + must have the same dimensions: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 1, + }); + + NOTE: when creating render target images, have in mind that some default values + are aligned with the default environment attributes in the sg_environment struct + that was passed into the sg_setup() call: + + - the default value for sg_image_desc.pixel_format is taken from + sg_environment.defaults.color_format + - the default value for sg_image_desc.sample_count is taken from + sg_environment.defaults.sample_count + - the default value for sg_image_desc.num_mipmaps is always 1 + + Next create an attachments object: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .depth_stencil.image = depth_img, + }); + + This attachments object is then passed into the sg_begin_pass() function + in place of the swapchain struct: + + sg_begin_pass(&(sg_pass){ .attachments = atts }); + + Swapchain and offscreen passes form dependency trees each with a swapchain + pass at the root, offscreen passes as nodes, and render target images as + dependencies between passes. + + sg_pass_action structs are used to define actions that should happen at the + start and end of rendering passes (such as clearing pass attachments to a + specific color or depth-value, or performing an MSAA resolve operation at + the end of a pass). + + A typical sg_pass_action object which clears the color attachment to black + might look like this: + + const sg_pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + This omits the defaults for the color attachment store action, and + the depth-stencil-attachments actions. The same pass action with the + defaults explicitly filled in would look like this: + + const sg_pass_action pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_STORE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + }, + .depth = = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = 1.0f, + }, + .stencil = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = 0 + } + }; + + With the sg_pass object and sg_pass_action struct in place everything + is ready now for the actual render pass: + + Using such this prepared sg_pass_action in a swapchain pass looks like + this: + + sg_begin_pass(&(sg_pass){ + .action = pass_action, + .swapchain = sglue_swapchain() + }); + ... + sg_end_pass(); + + ...of alternatively in one offscreen pass: + + sg_begin_pass(&(sg_pass){ + .action = pass_action, + .attachments = attachments, + }); + ... + sg_end_pass(); + + Offscreen rendering can also go into a mipmap, or a slice/face of + a cube-, array- or 3d-image (which some restrictions, for instance + it's not possible to create a 3D image with a depth/stencil pixel format, + these exceptions are generally caught by the sokol-gfx validation layer). + + The mipmap/slice selection happens at attachments creation time, for instance + to render into mipmap 2 of slice 3 of an array texture: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { + .image = color_img, + .mip_level = 2, + .slice = 3, + }, + .depth_stencil.image = depth_img, + }); + + If MSAA offscreen rendering is desired, the multi-sample rendering result + must be 'resolved' into a separate 'resolve image', before that image can + be used as texture. + + Creating a simple attachments object for multisampled rendering requires + 3 attachment images: the color attachment image which has a sample + count > 1, a resolve attachment image of the same size and pixel format + but a sample count == 1, and a depth/stencil attachment image with + the same size and sample count as the color attachment image: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 4, + }); + + ...create the attachments object: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .resolves[0].image = resolve_img, + .depth_stencil.image = depth_img, + }); + + If an attachments object defines a resolve image in a specific resolve attachment slot, + an 'msaa resolve operation' will happen in sg_end_pass(). + + In this scenario, the content of the MSAA color attachment doesn't need to be + preserved (since it's only needed inside sg_end_pass for the msaa-resolve), so + the .store_action should be set to "don't care": + + const sg_pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + The actual render pass looks as usual: + + sg_begin_pass(&(sg_pass){ .action = pass_action, .attachments = atts }); + ... + sg_end_pass(); + + ...after sg_end_pass() the only difference to the non-msaa scenario is that the + rendering result which is going to be used as texture in a followup pass is + in 'resolve_img', not in 'color_img' (in fact, trying to bind color_img as a + texture would result in a validation error). + + + ON COMPUTE PASSES + ================= + Compute passes are used to update the content of storage resources + (currently only storage buffers) by running compute shader code on + the GPU. This will almost always be more efficient than computing + that same data on the CPU and uploading the data via `sg_update_buffer()`. + + NOTE: compute passes are only supported on the following platforms and + backends: + + - macOS and iOS with Metal + - Windows with D3D11 and OpenGL + - Linux with OpenGL or GLES3.1+ + - Web with WebGPU + - Android with GLES3.1+ + + ...this means compute shaders can't be used on the following platform/backend + combos (the same restrictions apply to using storage buffers without compute + shaders): + + - macOS with GL + - iOS with GLES3 + - Web with WebGL2 + + A compute pass is started with: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + ...and finished with: + + sg_end_pass(); + + Typically the following functions will be called inside a compute pass: + + sg_apply_pipeline + sg_apply_bindings + sg_apply_uniforms + sg_dispatch + + The following functions are disallowed inside a compute pass + and will cause validation layer errors: + + sg_apply_viewport[f] + sg_apply_scissor_rect[f] + sg_draw + + Only special 'compute shaders' and 'compute pipelines' can be used in + compute passes. A compute shader only has a compute-function instead + of a vertex- and fragment-function pair, and it doesn't accept vertex- + and index-buffers as input, only storage-buffers, textures and non-filtering + samplers (more details on compute shaders in the following section). + + A compute pipeline is created by providing a compute shader object, + setting the .compute creation parameter to true and not defining any + 'render state': + + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .compute = true, + .shader = compute_shader, + }); + + The sg_apply_bindings and sg_apply_uniforms calls are the same as in + render passes, with the exception that no vertex- and index-buffers + can be bound in the sg_apply_bindings call. + + Finally to kick off a compute workload, call sg_dispatch with the + number of workgroups in the x, y and z-dimension: + + sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) + + Also see the following compute-shader samples: + + - https://floooh.github.io/sokol-webgpu/instancing-compute-sapp.html + - https://floooh.github.io/sokol-webgpu/computeboids-sapp.html + + + ON SHADER CREATION + ================== + sokol-gfx doesn't come with an integrated shader cross-compiler, instead + backend-specific shader sources or binary blobs need to be provided when + creating a shader object, along with reflection information about the + shader resource binding interface needed to bind sokol-gfx resources to the + proper shader inputs. + + The easiest way to provide all this shader creation data is to use the + sokol-shdc shader compiler tool to compile shaders from a common + GLSL syntax into backend-specific sources or binary blobs, along with + shader interface information and uniform blocks mapped to C structs. + + To create a shader using a C header which has been code-generated by sokol-shdc: + + // include the C header code-generated by sokol-shdc: + #include "myshader.glsl.h" + ... + + // create shader using a code-generated helper function from the C header: + sg_shader shd = sg_make_shader(myshader_shader_desc(sg_query_backend())); + + The samples in the 'sapp' subdirectory of the sokol-samples project + also use the sokol-shdc approach: + + https://github.com/floooh/sokol-samples/tree/master/sapp + + If you're planning to use sokol-shdc, you can stop reading here, instead + continue with the sokol-shdc documentation: + + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md + + To create shaders with backend-specific shader code or binary blobs, + the sg_make_shader() function requires the following information: + + - Shader code or shader binary blobs for the vertex- and fragment-, or the + compute-shader-stage: + - for the desktop GL backend, source code can be provided in '#version 410' or + '#version 430', version 430 is required when using storage buffers and + compute shaders support, but note that this is not available on macOS + - for the GLES3 backend, source code must be provided in '#version 300 es' syntax + - for the D3D11 backend, shaders can be provided as source or binary + blobs, the source code should be in HLSL4.0 (for compatibility with old + low-end GPUs) or preferrably in HLSL5.0 syntax, note that when + shader source code is provided for the D3D11 backend, sokol-gfx will + dynamically load 'd3dcompiler_47.dll' + - for the Metal backends, shaders can be provided as source or binary blobs, the + MSL version should be in 'metal-1.1' (other versions may work but are not tested) + - for the WebGPU backend, shaders must be provided as WGSL source code + - optionally the following shader-code related attributes can be provided: + - an entry function name (only on D3D11 or Metal, but not OpenGL) + - on D3D11 only, a compilation target (default is "vs_4_0" and "ps_4_0") + + - Information about the input vertex attributes used by the vertex shader, + most of that backend-specific: + - An optional 'base type' (float, signed-/unsigned-int) for each vertex + attribute. When provided, this used by the validation layer to check + that the CPU-side input vertex format is compatible with the input + vertex declaration of the vertex shader. + - Metal: no location information needed since vertex attributes are always bound + by their attribute location defined in the shader via '[[attribute(N)]]' + - WebGPU: no location information needed since vertex attributes are always + bound by their attribute location defined in the shader via `@location(N)` + - GLSL: vertex attribute names can be optionally provided, in that case their + location will be looked up by name, otherwise, the vertex attribute location + can be defined with 'layout(location = N)' + - D3D11: a 'semantic name' and 'semantic index' must be provided for each vertex + attribute, e.g. if the vertex attribute is defined as 'TEXCOORD1' in the shader, + the semantic name would be 'TEXCOORD', and the semantic index would be '1' + + NOTE that vertex attributes currently must not have gaps. This requirement + may be relaxed in the future. + + - Specifically for Metal compute shaders, the 'number of threads per threadgroup' + must be provided. Normally this is extracted by sokol-shdc from the GLSL + shader source code. For instance the following statement in the input + GLSL: + + layout(local_size_x=64, local_size_y=1, local_size_z=1) in; + + ...will be communicated to the sokol-gfx Metal backend in the + code-generated sg_shader_desc struct: + + (sg_shader_desc){ + .mtl_threads_per_threadgroup = { .x = 64, .y = 1, .z = 1 }, + } + + - Information about each uniform block used in the shader: + - the shader stage of the uniform block (vertex, fragment or compute) + - the size of the uniform block in number of bytes + - a memory layout hint (currently 'native' or 'std140') where 'native' defines a + backend-specific memory layout which shouldn't be used for cross-platform code. + Only std140 guarantees a backend-agnostic memory layout. + - a backend-specific bind slot: + - D3D11/HLSL: the buffer register N (`register(bN)`) where N is 0..7 + - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 0..7 + - WebGPU: the binding N in `@group(0) @binding(N)` where N is 0..15 + - For GLSL only: a description of the internal uniform block layout, which maps + member types and their offsets on the CPU side to uniform variable names + in the GLSL shader + - please also NOTE the documentation sections about UNIFORM DATA LAYOUT + and CROSS-BACKEND COMMON UNIFORM DATA LAYOUT below! + + - A description of each storage buffer used in the shader: + - the shader stage of the storage buffer + - a boolean 'readonly' flag, this is used for validation and hazard + tracking in some 3D backends. Note that in render passes, only + readonly storage buffer bindings are allowed. In compute passes, any + read/write storage buffer binding is assumbed to be written to by the + compute shader. + - a backend-specific bind slot: + - D3D11/HLSL: + - for readonly storage buffer bindings: the texture register N + (`register(tN)`) where N is 0..23 (in HLSL, readonly storage + buffers and textures share the same bind space for + 'shader resource views') + - for read/write storage buffer buffer bindings: the UAV register N + (`register(uN)`) where N is 0..7 (in HLSL, readwrite storage + buffers use their own bind space for 'unordered access views') + - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 8..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + - GL/GLSL: the buffer binding N in `layout(binding=N)` where N is 0..7 + - note that storage buffers are not supported on all backends + and platforms + + - A description of each texture/image used in the shader: + - the shader stage of the texture (vertex, fragment or compute) + - the expected image type: + - SG_IMAGETYPE_2D + - SG_IMAGETYPE_CUBE + - SG_IMAGETYPE_3D + - SG_IMAGETYPE_ARRAY + - the expected 'image sample type': + - SG_IMAGESAMPLETYPE_FLOAT + - SG_IMAGESAMPLETYPE_DEPTH + - SG_IMAGESAMPLETYPE_SINT + - SG_IMAGESAMPLETYPE_UINT + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT + - a flag whether the texture is expected to be multisampled + - a backend-specific bind slot: + - D3D11/HLSL: the texture register N (`register(tN)`) where N is 0..23 + (in HLSL, readonly storage buffers and texture share the same bind space) + - Metal/MSL: the texture bind slot N (`[[texture(N)]]`) where N is 0..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + - A description of each sampler used in the shader: + - the shader stage of the sampler (vertex, fragment or compute) + - the expected sampler type: + - SG_SAMPLERTYPE_FILTERING, + - SG_SAMPLERTYPE_NONFILTERING, + - SG_SAMPLERTYPE_COMPARISON, + - a backend-specific bind slot: + - D3D11/HLSL: the sampler register N (`register(sN)`) where N is 0..15 + - Metal/MSL: the sampler bind slot N (`[[sampler(N)]]`) where N is 0..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + - An array of 'image-sampler-pairs' used by the shader to sample textures, + for D3D11, Metal and WebGPU this is used for validation purposes to check + whether the texture and sampler are compatible with each other (especially + WebGPU is very picky about combining the correct + texture-sample-type with the correct sampler-type). For GLSL an + additional 'combined-image-sampler name' must be provided because 'OpenGL + style GLSL' cannot handle separate texture and sampler objects, but still + groups them into a traditional GLSL 'sampler object'. + + Compatibility rules for image-sample-type vs sampler-type are as follows: + + - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING) + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON + + Backend-specific bindslot ranges (not relevant when using sokol-shdc): + + - D3D11/HLSL: + - separate bindslot space per shader stage + - uniform blocks (as cbuffer): `register(b0..b7)` + - textures and readonly storage buffers: `register(t0..t23)` + - read/write storage buffers: `register(u0..u7)` + - samplers: `register(s0..s15)` + - Metal/MSL: + - separate bindslot space per shader stage + - uniform blocks: `[[buffer(0..7)]]` + - storage buffers: `[[buffer(8..15)]]` + - textures: `[[texture(0..15)]]` + - samplers: `[[sampler(0..15)]]` + - WebGPU/WGSL: + - common bindslot space across shader stages + - uniform blocks: `@group(0) @binding(0..15)` + - textures, samplers and storage buffers: `@group(1) @binding(0..127)` + - GL/GLSL: + - uniforms and image-samplers are bound by name + - storage buffers: `layout(std430, binding=0..7)` (common + bindslot space across shader stages) + + For example code of how to create backend-specific shader objects, + please refer to the following samples: + + - for D3D11: https://github.com/floooh/sokol-samples/tree/master/d3d11 + - for Metal: https://github.com/floooh/sokol-samples/tree/master/metal + - for OpenGL: https://github.com/floooh/sokol-samples/tree/master/glfw + - for GLES3: https://github.com/floooh/sokol-samples/tree/master/html5 + - for WebGPI: https://github.com/floooh/sokol-samples/tree/master/wgpu + + + ON SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT AND SG_SAMPLERTYPE_NONFILTERING + ======================================================================== + The WebGPU backend introduces the concept of 'unfilterable-float' textures, + which can only be combined with 'nonfiltering' samplers (this is a restriction + specific to WebGPU, but since the same sokol-gfx code should work across + all backend, the sokol-gfx validation layer also enforces this restriction + - the alternative would be undefined behaviour in some backend APIs on + some devices). + + The background is that some mobile devices (most notably iOS devices) can + not perform linear filtering when sampling textures with certain pixel + formats, most notable the 32F formats: + + - SG_PIXELFORMAT_R32F + - SG_PIXELFORMAT_RG32F + - SG_PIXELFORMAT_RGBA32F + + The information of whether a shader is going to be used with such an + unfilterable-float texture must already be provided in the sg_shader_desc + struct when creating the shader (see the above section "ON SHADER CREATION"). + + If you are using the sokol-shdc shader compiler, the information whether a + texture/sampler binding expects an 'unfilterable-float/nonfiltering' + texture/sampler combination cannot be inferred from the shader source + alone, you'll need to provide this hint via annotation-tags. For instance + here is an example from the ozz-skin-sapp.c sample shader which samples an + RGBA32F texture with skinning matrices in the vertex shader: + + ```glsl + @image_sample_type joint_tex unfilterable_float + uniform texture2D joint_tex; + @sampler_type smp nonfiltering + uniform sampler smp; + ``` + + This will result in SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT and + SG_SAMPLERTYPE_NONFILTERING being written to the code-generated + sg_shader_desc struct. + + + ON VERTEX FORMATS + ================= + Sokol-gfx implements the same strict mapping rules from CPU-side + vertex component formats to GPU-side vertex input data types: + + - float and packed normalized CPU-side formats must be used as + floating point base type in the vertex shader + - packed signed-integer CPU-side formats must be used as signed + integer base type in the vertex shader + - packed unsigned-integer CPU-side formats must be used as unsigned + integer base type in the vertex shader + + These mapping rules are enforced by the sokol-gfx validation layer, + but only when sufficient reflection information is provided in + `sg_shader_desc.attrs[].base_type`. This is the case when sokol-shdc + is used, otherwise the default base_type will be SG_SHADERATTRBASETYPE_UNDEFINED + which causes the sokol-gfx validation check to be skipped (of course you + can also provide the per-attribute base type information manually when + not using sokol-shdc). + + The detailed mapping rules from SG_VERTEXFORMAT_* to GLSL data types + are as follows: + + - FLOAT[*] => float, vec* + - BYTE4N => vec* (scaled to -1.0 .. +1.0) + - UBYTE4N => vec* (scaled to 0.0 .. +1.0) + - SHORT[*]N => vec* (scaled to -1.0 .. +1.0) + - USHORT[*]N => vec* (scaled to 0.0 .. +1.0) + - INT[*] => int, ivec* + - UINT[*] => uint, uvec* + - BYTE4 => int* + - UBYTE4 => uint* + - SHORT[*] => int* + - USHORT[*] => uint* + + NOTE that sokol-gfx only provides vertex formats with sizes of a multiple + of 4 (e.g. BYTE4N but not BYTE2N). This is because vertex components must + be 4-byte aligned anyway. + + + UNIFORM DATA LAYOUT: + ==================== + NOTE: if you use the sokol-shdc shader compiler tool, you don't need to worry + about the following details. + + The data that's passed into the sg_apply_uniforms() function must adhere to + specific layout rules so that the GPU shader finds the uniform block + items at the right offset. + + For the D3D11 and Metal backends, sokol-gfx only cares about the size of uniform + blocks, but not about the internal layout. The data will just be copied into + a uniform/constant buffer in a single operation and it's up you to arrange the + CPU-side layout so that it matches the GPU side layout. This also means that with + the D3D11 and Metal backends you are not limited to a 'cross-platform' subset + of uniform variable types. + + If you ever only use one of the D3D11, Metal *or* WebGPU backend, you can stop reading here. + + For the GL backends, the internal layout of uniform blocks matters though, + and you are limited to a small number of uniform variable types. This is + because sokol-gfx must be able to locate the uniform block members in order + to upload them to the GPU with glUniformXXX() calls. + + To describe the uniform block layout to sokol-gfx, the following information + must be passed to the sg_make_shader() call in the sg_shader_desc struct: + + - a hint about the used packing rule (either SG_UNIFORMLAYOUT_NATIVE or + SG_UNIFORMLAYOUT_STD140) + - a list of the uniform block members types in the correct order they + appear on the CPU side + + For example if the GLSL shader has the following uniform declarations: + + uniform mat4 mvp; + uniform vec2 offset0; + uniform vec2 offset1; + uniform vec2 offset2; + + ...and on the CPU side, there's a similar C struct: + + typedef struct { + float mvp[16]; + float offset0[2]; + float offset1[2]; + float offset2[2]; + } params_t; + + ...the uniform block description in the sg_shader_desc must look like this: + + sg_shader_desc desc = { + .vs.uniform_blocks[0] = { + .size = sizeof(params_t), + .layout = SG_UNIFORMLAYOUT_NATIVE, // this is the default and can be omitted + .uniforms = { + // order must be the same as in 'params_t': + [0] = { .name = "mvp", .type = SG_UNIFORMTYPE_MAT4 }, + [1] = { .name = "offset0", .type = SG_UNIFORMTYPE_VEC2 }, + [2] = { .name = "offset1", .type = SG_UNIFORMTYPE_VEC2 }, + [3] = { .name = "offset2", .type = SG_UNIFORMTYPE_VEC2 }, + } + } + }; + + With this information sokol-gfx can now compute the correct offsets of the data items + within the uniform block struct. + + The SG_UNIFORMLAYOUT_NATIVE packing rule works fine if only the GL backends are used, + but for proper D3D11/Metal/GL a subset of the std140 layout must be used which is + described in the next section: + + + CROSS-BACKEND COMMON UNIFORM DATA LAYOUT + ======================================== + For cross-platform / cross-3D-backend code it is important that the same uniform block + layout on the CPU side can be used for all sokol-gfx backends. To achieve this, + a common subset of the std140 layout must be used: + + - The uniform block layout hint in sg_shader_desc must be explicitly set to + SG_UNIFORMLAYOUT_STD140. + - Only the following GLSL uniform types can be used (with their associated sokol-gfx enums): + - float => SG_UNIFORMTYPE_FLOAT + - vec2 => SG_UNIFORMTYPE_FLOAT2 + - vec3 => SG_UNIFORMTYPE_FLOAT3 + - vec4 => SG_UNIFORMTYPE_FLOAT4 + - int => SG_UNIFORMTYPE_INT + - ivec2 => SG_UNIFORMTYPE_INT2 + - ivec3 => SG_UNIFORMTYPE_INT3 + - ivec4 => SG_UNIFORMTYPE_INT4 + - mat4 => SG_UNIFORMTYPE_MAT4 + - Alignment for those types must be as follows (in bytes): + - float => 4 + - vec2 => 8 + - vec3 => 16 + - vec4 => 16 + - int => 4 + - ivec2 => 8 + - ivec3 => 16 + - ivec4 => 16 + - mat4 => 16 + - Arrays are only allowed for the following types: vec4, int4, mat4. + + Note that the HLSL cbuffer layout rules are slightly different from the + std140 layout rules, this means that the cbuffer declarations in HLSL code + must be tweaked so that the layout is compatible with std140. + + The by far easiest way to tackle the common uniform block layout problem is + to use the sokol-shdc shader cross-compiler tool! + + + ON STORAGE BUFFERS + ================== + The two main purpose of storage buffers are: + + - to be populated by compute shaders with dynamically generated data + - for providing random-access data to all shader stages + + Storage buffers can be used to pass large amounts of random access structured + data from the CPU side to the shaders. They are similar to data textures, but are + more convenient to use both on the CPU and shader side since they can be accessed + in shaders as as a 1-dimensional array of struct items. + + Storage buffers are *NOT* supported on the following platform/backend combos: + + - macOS+GL (because storage buffers require GL 4.3, while macOS only goes up to GL 4.1) + - platforms which only support a GLES3.0 context (WebGL2 and iOS) + + To use storage buffers, the following steps are required: + + - write a shader which uses storage buffers (vertex- and fragment-shaders + can only read from storage buffers, while compute-shaders can both read + and write storage buffers) + - create one or more storage buffers via sg_make_buffer() with the + buffer type SG_BUFFERTYPE_STORAGEBUFFER + - when creating a shader via sg_make_shader(), populate the sg_shader_desc + struct with binding info (when using sokol-shdc, this step will be taken care + of automatically) + - which storage buffer bind slots on the vertex-, fragment- or compute-stage + are occupied + - whether the storage buffer on that bind slot is readonly (readonly + bindings are required for vertex- and fragment-shaders, and in compute + shaders the readonly flag is used to control hazard tracking in some + 3D backends) + + - when calling sg_apply_bindings(), apply the matching bind slots with the previously + created storage buffers + - ...and that's it. + + For more details, see the following backend-agnostic sokol samples: + + - simple vertex pulling from a storage buffer: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.glsl + - instanced rendering via storage buffers (vertex- and instance-pulling): + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.glsl + - storage buffers both on the vertex- and fragment-stage: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.glsl + - the Ozz animation sample rewritten to pull all rendering data from storage buffers: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.cc + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.glsl + - the instancing sample modified to use compute shaders: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.glsl + - the Compute Boids sample ported to sokol-gfx: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.glsl + + ...also see the following backend-specific vertex pulling samples (those also don't use sokol-shdc): + + - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/vertexpulling-d3d11.c + - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/vertexpulling-glfw.c + - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/vertexpulling-metal.c + - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/vertexpulling-wgpu.c + + ...and the backend specific compute shader samples: + + - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/instancing-compute-d3d11.c + - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/instancing-compute-glfw.c + - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/instancing-compute-metal.c + - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/instancing-compute-wgpu.c + + Storage buffer shader authoring caveats when using sokol-shdc: + + - declare a read-only storage buffer interface block with `layout(binding=N) readonly buffer [name] { ... }` + (where 'N' is the index in `sg_bindings.storage_buffers[N]`) + - ...or a read/write storage buffer interface block with `layout(binding=N) buffer [name] { ... }` + - declare a struct which describes a single array item in the storage buffer interface block + - only put a single flexible array member into the storage buffer interface block + + E.g. a complete example in 'sokol-shdc GLSL': + + ```glsl + @vs + // declare a struct: + struct sb_vertex { + vec3 pos; + vec4 color; + } + // declare a buffer interface block with a single flexible struct array: + layout(binding=0) readonly buffer vertices { + sb_vertex vtx[]; + } + // in the shader function, access the storage buffer like this: + void main() { + vec3 pos = vtx[gl_VertexIndex].pos; + ... + } + @end + ``` + + In a compute shader you can read and write the same item in the same + storage buffer (but you'll have to be careful for random access since + many threads of the same compute function run in parallel): + + @cs + struct sb_item { + vec3 pos; + vec3 vel; + } + layout(binding=0) buffer items_ssbo { + sb_item items[]; + } + layout(local_size_x=64, local_size_y=1, local_size_z=1) in; + void main() { + uint idx = gl_GlobalInvocationID.x; + vec3 pos = items[idx].pos; + ... + items[idx].pos = pos; + } + @end + + Backend-specific storage-buffer caveats (not relevant when using sokol-shdc): + + D3D11: + - storage buffers are created as 'raw' Byte Address Buffers + (https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-intro#raw-views-of-buffers) + - in HLSL, use a ByteAddressBuffer for readonly access of the buffer content: + (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-byteaddressbuffer) + - ...or RWByteAddressBuffer for read/write access: + (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwbyteaddressbuffer) + - readonly-storage buffers and textures are both bound as 'shader-resource-view' and + share the same bind slots (declared as `register(tN)` in HLSL), where N must be in the range 0..23) + - read/write storage buffers are bound as 'unordered-access-view' (declared as `register(uN)` in HLSL + where N is in the range 0..7) + + Metal: + - in Metal there is no internal difference between vertex-, uniform- and + storage-buffers, all are bound to the same 'buffer bind slots' with the + following reserved ranges: + - vertex shader stage: + - uniform buffers: slots 0..7 + - storage buffers: slots 8..15 + - vertex buffers: slots 15..23 + - fragment shader stage: + - uniform buffers: slots 0..7 + - storage buffers: slots 8..15 + - this means in MSL, storage buffer bindings start at [[buffer(8)]] both in + the vertex and fragment stage + + GL: + - the GL backend doesn't use name-lookup to find storage buffer bindings, this + means you must annotate buffers with `layout(std430, binding=N)` in GLSL + - ...where N is 0..7 in the vertex shader, and 8..15 in the fragment shader + + WebGPU: + - in WGSL, textures, samplers and storage buffers all use a shared + bindspace across all shader stages on bindgroup 1: + + `@group(1) @binding(0..127) + + + TRACE HOOKS: + ============ + sokol_gfx.h optionally allows to install "trace hook" callbacks for + each public API functions. When a public API function is called, and + a trace hook callback has been installed for this function, the + callback will be invoked with the parameters and result of the function. + This is useful for things like debugging- and profiling-tools, or + keeping track of resource creation and destruction. + + To use the trace hook feature: + + --- Define SOKOL_TRACE_HOOKS before including the implementation. + + --- Setup an sg_trace_hooks structure with your callback function + pointers (keep all function pointers you're not interested + in zero-initialized), optionally set the user_data member + in the sg_trace_hooks struct. + + --- Install the trace hooks by calling sg_install_trace_hooks(), + the return value of this function is another sg_trace_hooks + struct which contains the previously set of trace hooks. + You should keep this struct around, and call those previous + functions pointers from your own trace callbacks for proper + chaining. + + As an example of how trace hooks are used, have a look at the + imgui/sokol_gfx_imgui.h header which implements a realtime + debugging UI for sokol_gfx.h on top of Dear ImGui. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sg_setup(&(sg_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_gfx.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sg_setup(&(sg_desc){ .logger.func = slog_func }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sg' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gfx like this: + + sg_setup(&(sg_desc){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + COMMIT LISTENERS + ================ + It's possible to hook callback functions into sokol-gfx which are called from + inside sg_commit() in unspecified order. This is mainly useful for libraries + that build on top of sokol_gfx.h to be notified about the end/start of a frame. + + To add a commit listener, call: + + static void my_commit_listener(void* user_data) { + ... + } + + bool success = sg_add_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + The function returns false if the internal array of commit listeners is full, + or the same commit listener had already been added. + + If the function returns true, my_commit_listener() will be called each frame + from inside sg_commit(). + + By default, 1024 distinct commit listeners can be added, but this number + can be tweaked in the sg_setup() call: + + sg_setup(&(sg_desc){ + .max_commit_listeners = 2048, + }); + + An sg_commit_listener item is equal to another if both the function + pointer and user_data field are equal. + + To remove a commit listener: + + bool success = sg_remove_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + ...where the .func and .user_data field are equal to a previous + sg_add_commit_listener() call. The function returns true if the commit + listener item was found and removed, and false otherwise. + + + RESOURCE CREATION AND DESTRUCTION IN DETAIL + =========================================== + The 'vanilla' way to create resource objects is with the 'make functions': + + sg_buffer sg_make_buffer(const sg_buffer_desc* desc) + sg_image sg_make_image(const sg_image_desc* desc) + sg_sampler sg_make_sampler(const sg_sampler_desc* desc) + sg_shader sg_make_shader(const sg_shader_desc* desc) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) + sg_attachments sg_make_attachments(const sg_attachments_desc* desc) + + This will result in one of three cases: + + 1. The returned handle is invalid. This happens when there are no more + free slots in the resource pool for this resource type. An invalid + handle is associated with the INVALID resource state, for instance: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID) { + // buffer pool is exhausted + } + + 2. The returned handle is valid, but creating the underlying resource + has failed for some reason. This results in a resource object in the + FAILED state. The reason *why* resource creation has failed differ + by resource type. Look for log messages with more details. A failed + resource state can be checked with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED) { + // creating the resource has failed + } + + 3. And finally, if everything goes right, the returned resource is + in resource state VALID and ready to use. This can be checked + with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID) { + // creating the resource has failed + } + + When calling the 'make functions', the created resource goes through a number + of states: + + - INITIAL: the resource slot associated with the new resource is currently + free (technically, there is no resource yet, just an empty pool slot) + - ALLOC: a handle for the new resource has been allocated, this just means + a pool slot has been reserved. + - VALID or FAILED: in VALID state any 3D API backend resource objects have + been successfully created, otherwise if anything went wrong, the resource + will be in FAILED state. + + Sometimes it makes sense to first grab a handle, but initialize the + underlying resource at a later time. For instance when loading data + asynchronously from a slow data source, you may know what buffers and + textures are needed at an early stage of the loading process, but actually + loading the buffer or texture content can only be completed at a later time. + + For such situations, sokol-gfx resource objects can be created in two steps. + You can allocate a handle upfront with one of the 'alloc functions': + + sg_buffer sg_alloc_buffer(void) + sg_image sg_alloc_image(void) + sg_sampler sg_alloc_sampler(void) + sg_shader sg_alloc_shader(void) + sg_pipeline sg_alloc_pipeline(void) + sg_attachments sg_alloc_attachments(void) + + This will return a handle with the underlying resource object in the + ALLOC state: + + sg_image img = sg_alloc_image(); + if (sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC) { + // allocating an image handle has succeeded, otherwise + // the image pool is full + } + + Such an 'incomplete' handle can be used in most sokol-gfx rendering functions + without doing any harm, sokol-gfx will simply skip any rendering operation + that involve resources which are not in VALID state. + + At a later time (for instance once the texture has completed loading + asynchronously), the resource creation can be completed by calling one of + the 'init functions', those functions take an existing resource handle and + 'desc struct': + + void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc) + void sg_init_image(sg_image img, const sg_image_desc* desc) + void sg_init_sampler(sg_sampler smp, const sg_sampler_desc* desc) + void sg_init_shader(sg_shader shd, const sg_shader_desc* desc) + void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc) + void sg_init_attachments(sg_attachments atts, const sg_attachments_desc* desc) + + The init functions expect a resource in ALLOC state, and after the function + returns, the resource will be either in VALID or FAILED state. Calling + an 'alloc function' followed by the matching 'init function' is fully + equivalent with calling the 'make function' alone. + + Destruction can also happen as a two-step process. The 'uninit functions' + will put a resource object from the VALID or FAILED state back into the + ALLOC state: + + void sg_uninit_buffer(sg_buffer buf) + void sg_uninit_image(sg_image img) + void sg_uninit_sampler(sg_sampler smp) + void sg_uninit_shader(sg_shader shd) + void sg_uninit_pipeline(sg_pipeline pip) + void sg_uninit_attachments(sg_attachments pass) + + Calling the 'uninit functions' with a resource that is not in the VALID or + FAILED state is a no-op. + + To finally free the pool slot for recycling call the 'dealloc functions': + + void sg_dealloc_buffer(sg_buffer buf) + void sg_dealloc_image(sg_image img) + void sg_dealloc_sampler(sg_sampler smp) + void sg_dealloc_shader(sg_shader shd) + void sg_dealloc_pipeline(sg_pipeline pip) + void sg_dealloc_attachments(sg_attachments atts) + + Calling the 'dealloc functions' on a resource that's not in ALLOC state is + a no-op, but will generate a warning log message. + + Calling an 'uninit function' and 'dealloc function' in sequence is equivalent + with calling the associated 'destroy function': + + void sg_destroy_buffer(sg_buffer buf) + void sg_destroy_image(sg_image img) + void sg_destroy_sampler(sg_sampler smp) + void sg_destroy_shader(sg_shader shd) + void sg_destroy_pipeline(sg_pipeline pip) + void sg_destroy_attachments(sg_attachments atts) + + The 'destroy functions' can be called on resources in any state and generally + do the right thing (for instance if the resource is in ALLOC state, the destroy + function will be equivalent to the 'dealloc function' and skip the 'uninit part'). + + And finally to close the circle, the 'fail functions' can be called to manually + put a resource in ALLOC state into the FAILED state: + + sg_fail_buffer(sg_buffer buf) + sg_fail_image(sg_image img) + sg_fail_sampler(sg_sampler smp) + sg_fail_shader(sg_shader shd) + sg_fail_pipeline(sg_pipeline pip) + sg_fail_attachments(sg_attachments atts) + + This is recommended if anything went wrong outside of sokol-gfx during asynchronous + resource setup (for instance a file loading operation failed). In this case, + the 'fail function' should be called instead of the 'init function'. + + Calling a 'fail function' on a resource that's not in ALLOC state is a no-op, + but will generate a warning log message. + + NOTE: that two-step resource creation usually only makes sense for buffers + and images, but not for samplers, shaders, pipelines or attachments. Most notably, trying + to create a pipeline object with a shader that's not in VALID state will + trigger a validation layer error, or if the validation layer is disabled, + result in a pipeline object in FAILED state. Same when trying to create + an attachments object with invalid image objects. + + + WEBGPU CAVEATS + ============== + For a general overview and design notes of the WebGPU backend see: + + https://floooh.github.io/2023/10/16/sokol-webgpu.html + + In general, don't expect an automatic speedup when switching from the WebGL2 + backend to the WebGPU backend. Some WebGPU functions currently actually + have a higher CPU overhead than similar WebGL2 functions, leading to the + paradoxical situation that some WebGPU code may be slower than similar WebGL2 + code. + + - when writing WGSL shader code by hand, a specific bind-slot convention + must be used: + + All uniform block structs must use `@group(0)` and bindings in the + range 0..15 + + @group(0) @binding(0..15) + + All textures, samplers and storage buffers must use `@group(1)` and + bindings must be in the range 0..127: + + @group(1) @binding(0..127) + + Note that the number of texture, sampler and storage buffer bindings + is still limited despite the large bind range: + + - up to 16 textures and sampler across all shader stages + - up to 8 storage buffers across all shader stages + + If you use sokol-shdc to generate WGSL shader code, you don't need to worry + about the above binding conventions since sokol-shdc. + + - The sokol-gfx WebGPU backend uses the sg_desc.uniform_buffer_size item + to allocate a single per-frame uniform buffer which must be big enough + to hold all data written by sg_apply_uniforms() during a single frame, + including a worst-case 256-byte alignment (e.g. each sg_apply_uniform + call will cost at least 256 bytes of uniform buffer size). The default size + is 4 MB, which is enough for 16384 sg_apply_uniform() calls per + frame (assuming the uniform data 'payload' is less than 256 bytes + per call). These rules are the same as for the Metal backend, so if + you are already using the Metal backend you'll be fine. + + - sg_apply_bindings(): the sokol-gfx WebGPU backend implements a bindgroup + cache to prevent excessive creation and destruction of BindGroup objects + when calling sg_apply_bindings(). The number of slots in the bindgroups + cache is defined in sg_desc.wgpu_bindgroups_cache_size when calling + sg_setup. The cache size must be a power-of-2 number, with the default being + 1024. The bindgroups cache behaviour can be observed by calling the new + function sg_query_frame_stats(), where the following struct items are + of interest: + + .wgpu.num_bindgroup_cache_hits + .wgpu.num_bindgroup_cache_misses + .wgpu.num_bindgroup_cache_collisions + .wgpu_num_bindgroup_cache_invalidates + .wgpu.num_bindgroup_cache_vs_hash_key_mismatch + + The value to pay attention to is `.wgpu.num_bindgroup_cache_collisions`, + if this number is consistently higher than a few percent of the + .wgpu.num_set_bindgroup value, it might be a good idea to bump the + bindgroups cache size to the next power-of-2. + + - sg_apply_viewport(): WebGPU currently has a unique restriction that viewport + rectangles must be contained entirely within the framebuffer. As a shitty + workaround sokol_gfx.h will clip incoming viewport rectangles against + the framebuffer, but this will distort the clipspace-to-screenspace mapping. + There's no proper way to handle this inside sokol_gfx.h, this must be fixed + in a future WebGPU update (see: https://github.com/gpuweb/gpuweb/issues/373 + and https://github.com/gpuweb/gpuweb/pull/5025) + + - The sokol shader compiler generally adds `diagnostic(off, derivative_uniformity);` + into the WGSL output. Currently only the Chrome WebGPU implementation seems + to recognize this. + + - Likewise, the following sokol-gfx pixel formats are not supported in WebGPU: + R16, R16SN, RG16, RG16SN, RGBA16, RGBA16SN. + Unlike unsupported vertex formats, unsupported pixel formats can be queried + in cross-backend code via sg_query_pixel_format() though. + + - The Emscripten WebGPU shim currently doesn't support the Closure minification + post-link-step (e.g. currently the emcc argument '--closure 1' or '--closure 2' + will generate broken Javascript code. + + - sokol-gfx requires the WebGPU device feature `depth32float-stencil8` to be enabled + (this should be widely supported) + + - sokol-gfx expects that the WebGPU device feature `float32-filterable` to *not* be + enabled (since this would exclude all iOS devices) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_GFX_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GFX_API_DECL) +#define SOKOL_GFX_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GFX_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GFX_IMPL) +#define SOKOL_GFX_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GFX_API_DECL __declspec(dllimport) +#else +#define SOKOL_GFX_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Resource id typedefs: + + sg_buffer: vertex- and index-buffers + sg_image: images used as textures and render-pass attachments + sg_sampler sampler objects describing how a texture is sampled in a shader + sg_shader: vertex- and fragment-shaders and shader interface information + sg_pipeline: associated shader and vertex-layouts, and render states + sg_attachments: a baked collection of render pass attachment images + + Instead of pointers, resource creation functions return a 32-bit + handle which uniquely identifies the resource object. + + The 32-bit resource id is split into a 16-bit pool index in the lower bits, + and a 16-bit 'generation counter' in the upper bits. The index allows fast + pool lookups, and combined with the generation-counter it allows to detect + 'dangling accesses' (trying to use an object which no longer exists, and + its pool slot has been reused for a new object) + + The resource ids are wrapped into a strongly-typed struct so that + trying to pass an incompatible resource id is a compile error. +*/ +typedef struct sg_buffer { uint32_t id; } sg_buffer; +typedef struct sg_image { uint32_t id; } sg_image; +typedef struct sg_sampler { uint32_t id; } sg_sampler; +typedef struct sg_shader { uint32_t id; } sg_shader; +typedef struct sg_pipeline { uint32_t id; } sg_pipeline; +typedef struct sg_attachments { uint32_t id; } sg_attachments; + +/* + sg_range is a pointer-size-pair struct used to pass memory blobs into + sokol-gfx. When initialized from a value type (array or struct), you can + use the SG_RANGE() macro to build an sg_range struct. For functions which + take either a sg_range pointer, or a (C++) sg_range reference, use the + SG_RANGE_REF macro as a solution which compiles both in C and C++. +*/ +typedef struct sg_range { + const void* ptr; + size_t size; +} sg_range; + +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) // /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' +#pragma warning(disable:4204) // VS2015: nonstandard extension used: non-constant aggregate initializer +#endif +#if defined(__cplusplus) +#define SG_RANGE(x) sg_range{ &x, sizeof(x) } +#define SG_RANGE_REF(x) sg_range{ &x, sizeof(x) } +#else +#define SG_RANGE(x) (sg_range){ &x, sizeof(x) } +#define SG_RANGE_REF(x) &(sg_range){ &x, sizeof(x) } +#endif + +// various compile-time constants in the public API +enum { + SG_INVALID_ID = 0, + SG_NUM_INFLIGHT_FRAMES = 2, + SG_MAX_COLOR_ATTACHMENTS = 4, + SG_MAX_UNIFORMBLOCK_MEMBERS = 16, + SG_MAX_VERTEX_ATTRIBUTES = 16, + SG_MAX_MIPMAPS = 16, + SG_MAX_TEXTUREARRAY_LAYERS = 128, + SG_MAX_UNIFORMBLOCK_BINDSLOTS = 8, + SG_MAX_VERTEXBUFFER_BINDSLOTS = 8, + SG_MAX_IMAGE_BINDSLOTS = 16, + SG_MAX_SAMPLER_BINDSLOTS = 16, + SG_MAX_STORAGEBUFFER_BINDSLOTS = 8, + SG_MAX_IMAGE_SAMPLER_PAIRS = 16, +}; + +/* + sg_color + + An RGBA color value. +*/ +typedef struct sg_color { float r, g, b, a; } sg_color; + +/* + sg_backend + + The active 3D-API backend, use the function sg_query_backend() + to get the currently active backend. +*/ +typedef enum sg_backend { + SG_BACKEND_GLCORE, + SG_BACKEND_GLES3, + SG_BACKEND_D3D11, + SG_BACKEND_METAL_IOS, + SG_BACKEND_METAL_MACOS, + SG_BACKEND_METAL_SIMULATOR, + SG_BACKEND_WGPU, + SG_BACKEND_DUMMY, +} sg_backend; + +/* + sg_pixel_format + + sokol_gfx.h basically uses the same pixel formats as WebGPU, since these + are supported on most newer GPUs. + + A pixelformat name consist of three parts: + + - components (R, RG, RGB or RGBA) + - bit width per component (8, 16 or 32) + - component data type: + - unsigned normalized (no postfix) + - signed normalized (SN postfix) + - unsigned integer (UI postfix) + - signed integer (SI postfix) + - float (F postfix) + + Not all pixel formats can be used for everything, call sg_query_pixelformat() + to inspect the capabilities of a given pixelformat. The function returns + an sg_pixelformat_info struct with the following members: + + - sample: the pixelformat can be sampled as texture at least with + nearest filtering + - filter: the pixelformat can be sampled as texture with linear + filtering + - render: the pixelformat can be used as render-pass attachment + - blend: blending is supported when used as render-pass attachment + - msaa: multisample-antialiasing is supported when used + as render-pass attachment + - depth: the pixelformat can be used for depth-stencil attachments + - compressed: this is a block-compressed format + - bytes_per_pixel: the numbers of bytes in a pixel (0 for compressed formats) + + The default pixel format for texture images is SG_PIXELFORMAT_RGBA8. + + The default pixel format for render target images is platform-dependent + and taken from the sg_environment struct passed into sg_setup(). Typically + the default formats are: + + - for the Metal, D3D11 and WebGPU backends: SG_PIXELFORMAT_BGRA8 + - for GL backends: SG_PIXELFORMAT_RGBA8 +*/ +typedef enum sg_pixel_format { + _SG_PIXELFORMAT_DEFAULT, // value 0 reserved for default-init + SG_PIXELFORMAT_NONE, + + SG_PIXELFORMAT_R8, + SG_PIXELFORMAT_R8SN, + SG_PIXELFORMAT_R8UI, + SG_PIXELFORMAT_R8SI, + + SG_PIXELFORMAT_R16, + SG_PIXELFORMAT_R16SN, + SG_PIXELFORMAT_R16UI, + SG_PIXELFORMAT_R16SI, + SG_PIXELFORMAT_R16F, + SG_PIXELFORMAT_RG8, + SG_PIXELFORMAT_RG8SN, + SG_PIXELFORMAT_RG8UI, + SG_PIXELFORMAT_RG8SI, + + SG_PIXELFORMAT_R32UI, + SG_PIXELFORMAT_R32SI, + SG_PIXELFORMAT_R32F, + SG_PIXELFORMAT_RG16, + SG_PIXELFORMAT_RG16SN, + SG_PIXELFORMAT_RG16UI, + SG_PIXELFORMAT_RG16SI, + SG_PIXELFORMAT_RG16F, + SG_PIXELFORMAT_RGBA8, + SG_PIXELFORMAT_SRGB8A8, + SG_PIXELFORMAT_RGBA8SN, + SG_PIXELFORMAT_RGBA8UI, + SG_PIXELFORMAT_RGBA8SI, + SG_PIXELFORMAT_BGRA8, + SG_PIXELFORMAT_RGB10A2, + SG_PIXELFORMAT_RG11B10F, + SG_PIXELFORMAT_RGB9E5, + + SG_PIXELFORMAT_RG32UI, + SG_PIXELFORMAT_RG32SI, + SG_PIXELFORMAT_RG32F, + SG_PIXELFORMAT_RGBA16, + SG_PIXELFORMAT_RGBA16SN, + SG_PIXELFORMAT_RGBA16UI, + SG_PIXELFORMAT_RGBA16SI, + SG_PIXELFORMAT_RGBA16F, + + SG_PIXELFORMAT_RGBA32UI, + SG_PIXELFORMAT_RGBA32SI, + SG_PIXELFORMAT_RGBA32F, + + // NOTE: when adding/removing pixel formats before DEPTH, also update sokol_app.h/_SAPP_PIXELFORMAT_* + SG_PIXELFORMAT_DEPTH, + SG_PIXELFORMAT_DEPTH_STENCIL, + + // NOTE: don't put any new compressed format in front of here + SG_PIXELFORMAT_BC1_RGBA, + SG_PIXELFORMAT_BC2_RGBA, + SG_PIXELFORMAT_BC3_RGBA, + SG_PIXELFORMAT_BC3_SRGBA, + SG_PIXELFORMAT_BC4_R, + SG_PIXELFORMAT_BC4_RSN, + SG_PIXELFORMAT_BC5_RG, + SG_PIXELFORMAT_BC5_RGSN, + SG_PIXELFORMAT_BC6H_RGBF, + SG_PIXELFORMAT_BC6H_RGBUF, + SG_PIXELFORMAT_BC7_RGBA, + SG_PIXELFORMAT_BC7_SRGBA, + SG_PIXELFORMAT_ETC2_RGB8, + SG_PIXELFORMAT_ETC2_SRGB8, + SG_PIXELFORMAT_ETC2_RGB8A1, + SG_PIXELFORMAT_ETC2_RGBA8, + SG_PIXELFORMAT_ETC2_SRGB8A8, + SG_PIXELFORMAT_EAC_R11, + SG_PIXELFORMAT_EAC_R11SN, + SG_PIXELFORMAT_EAC_RG11, + SG_PIXELFORMAT_EAC_RG11SN, + + SG_PIXELFORMAT_ASTC_4x4_RGBA, + SG_PIXELFORMAT_ASTC_4x4_SRGBA, + + _SG_PIXELFORMAT_NUM, + _SG_PIXELFORMAT_FORCE_U32 = 0x7FFFFFFF +} sg_pixel_format; + +/* + Runtime information about a pixel format, returned by sg_query_pixelformat(). +*/ +typedef struct sg_pixelformat_info { + bool sample; // pixel format can be sampled in shaders at least with nearest filtering + bool filter; // pixel format can be sampled with linear filtering + bool render; // pixel format can be used as render-pass attachment + bool blend; // pixel format supports alpha-blending when used as render-pass attachment + bool msaa; // pixel format supports MSAA when used as render-pass attachment + bool depth; // pixel format is a depth format + bool compressed; // true if this is a hardware-compressed format + int bytes_per_pixel; // NOTE: this is 0 for compressed formats, use sg_query_row_pitch() / sg_query_surface_pitch() as alternative +} sg_pixelformat_info; + +/* + Runtime information about available optional features, returned by sg_query_features() +*/ +typedef struct sg_features { + bool origin_top_left; // framebuffer- and texture-origin is in top left corner + bool image_clamp_to_border; // border color and clamp-to-border uv-wrap mode is supported + bool mrt_independent_blend_state; // multiple-render-target rendering can use per-render-target blend state + bool mrt_independent_write_mask; // multiple-render-target rendering can use per-render-target color write masks + bool compute; // storage buffers and compute shaders are supported + bool msaa_image_bindings; // if true, multisampled images can be bound as texture resources +} sg_features; + +/* + Runtime information about resource limits, returned by sg_query_limit() +*/ +typedef struct sg_limits { + int max_image_size_2d; // max width/height of SG_IMAGETYPE_2D images + int max_image_size_cube; // max width/height of SG_IMAGETYPE_CUBE images + int max_image_size_3d; // max width/height/depth of SG_IMAGETYPE_3D images + int max_image_size_array; // max width/height of SG_IMAGETYPE_ARRAY images + int max_image_array_layers; // max number of layers in SG_IMAGETYPE_ARRAY images + int max_vertex_attrs; // max number of vertex attributes, clamped to SG_MAX_VERTEX_ATTRIBUTES + int gl_max_vertex_uniform_components; // <= GL_MAX_VERTEX_UNIFORM_COMPONENTS (only on GL backends) + int gl_max_combined_texture_image_units; // <= GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (only on GL backends) +} sg_limits; + +/* + sg_resource_state + + The current state of a resource in its resource pool. + Resources start in the INITIAL state, which means the + pool slot is unoccupied and can be allocated. When a resource is + created, first an id is allocated, and the resource pool slot + is set to state ALLOC. After allocation, the resource is + initialized, which may result in the VALID or FAILED state. The + reason why allocation and initialization are separate is because + some resource types (e.g. buffers and images) might be asynchronously + initialized by the user application. If a resource which is not + in the VALID state is attempted to be used for rendering, rendering + operations will silently be dropped. + + The special INVALID state is returned in sg_query_xxx_state() if no + resource object exists for the provided resource id. +*/ +typedef enum sg_resource_state { + SG_RESOURCESTATE_INITIAL, + SG_RESOURCESTATE_ALLOC, + SG_RESOURCESTATE_VALID, + SG_RESOURCESTATE_FAILED, + SG_RESOURCESTATE_INVALID, + _SG_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF +} sg_resource_state; + +/* + sg_usage + + A resource usage hint describing the update strategy of + buffers and images. This is used in the sg_buffer_desc.usage + and sg_image_desc.usage members when creating buffers + and images: + + SG_USAGE_IMMUTABLE: the resource will never be updated with + new (CPU-side) data, instead the content of the + resource must be provided on creation + SG_USAGE_DYNAMIC: the resource will be updated infrequently + with new data (this could range from "once + after creation", to "quite often but not + every frame") + SG_USAGE_STREAM: the resource will be updated each frame + with new content + + The rendering backends use this hint to prevent that the + CPU needs to wait for the GPU when attempting to update + a resource that might be currently accessed by the GPU. + + Resource content is updated with the functions sg_update_buffer() or + sg_append_buffer() for buffer objects, and sg_update_image() for image + objects. For the sg_update_*() functions, only one update is allowed per + frame and resource object, while sg_append_buffer() can be called + multiple times per frame on the same buffer. The application must update + all data required for rendering (this means that the update data can be + smaller than the resource size, if only a part of the overall resource + size is used for rendering, you only need to make sure that the data that + *is* used is valid). + + The default usage is SG_USAGE_IMMUTABLE. +*/ +typedef enum sg_usage { + _SG_USAGE_DEFAULT, // value 0 reserved for default-init + SG_USAGE_IMMUTABLE, + SG_USAGE_DYNAMIC, + SG_USAGE_STREAM, + _SG_USAGE_NUM, + _SG_USAGE_FORCE_U32 = 0x7FFFFFFF +} sg_usage; + +/* + sg_buffer_type + + Indicates whether a buffer will be bound as vertex-, + index- or storage-buffer. + + Used in the sg_buffer_desc.type member when creating a buffer. + + The default value is SG_BUFFERTYPE_VERTEXBUFFER. +*/ +typedef enum sg_buffer_type { + _SG_BUFFERTYPE_DEFAULT, // value 0 reserved for default-init + SG_BUFFERTYPE_VERTEXBUFFER, + SG_BUFFERTYPE_INDEXBUFFER, + SG_BUFFERTYPE_STORAGEBUFFER, + _SG_BUFFERTYPE_NUM, + _SG_BUFFERTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_buffer_type; + +/* + sg_index_type + + Indicates whether indexed rendering (fetching vertex-indices from an + index buffer) is used, and if yes, the index data type (16- or 32-bits). + + This is used in the sg_pipeline_desc.index_type member when creating a + pipeline object. + + The default index type is SG_INDEXTYPE_NONE. +*/ +typedef enum sg_index_type { + _SG_INDEXTYPE_DEFAULT, // value 0 reserved for default-init + SG_INDEXTYPE_NONE, + SG_INDEXTYPE_UINT16, + SG_INDEXTYPE_UINT32, + _SG_INDEXTYPE_NUM, + _SG_INDEXTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_index_type; + +/* + sg_image_type + + Indicates the basic type of an image object (2D-texture, cubemap, + 3D-texture or 2D-array-texture). Used in the sg_image_desc.type member when + creating an image, and in sg_shader_image_desc to describe a sampled texture + in the shader (both must match and will be checked in the validation layer + when calling sg_apply_bindings). + + The default image type when creating an image is SG_IMAGETYPE_2D. +*/ +typedef enum sg_image_type { + _SG_IMAGETYPE_DEFAULT, // value 0 reserved for default-init + SG_IMAGETYPE_2D, + SG_IMAGETYPE_CUBE, + SG_IMAGETYPE_3D, + SG_IMAGETYPE_ARRAY, + _SG_IMAGETYPE_NUM, + _SG_IMAGETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_image_type; + +/* + sg_image_sample_type + + The basic data type of a texture sample as expected by a shader. + Must be provided in sg_shader_image and used by the validation + layer in sg_apply_bindings() to check if the provided image object + is compatible with what the shader expects. Apart from the sokol-gfx + validation layer, WebGPU is the only backend API which actually requires + matching texture and sampler type to be provided upfront for validation + (other 3D APIs treat texture/sampler type mismatches as undefined behaviour). + + NOTE that the following texture pixel formats require the use + of SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT, combined with a sampler + of type SG_SAMPLERTYPE_NONFILTERING: + + - SG_PIXELFORMAT_R32F + - SG_PIXELFORMAT_RG32F + - SG_PIXELFORMAT_RGBA32F + + (when using sokol-shdc, also check out the meta tags `@image_sample_type` + and `@sampler_type`) +*/ +typedef enum sg_image_sample_type { + _SG_IMAGESAMPLETYPE_DEFAULT, // value 0 reserved for default-init + SG_IMAGESAMPLETYPE_FLOAT, + SG_IMAGESAMPLETYPE_DEPTH, + SG_IMAGESAMPLETYPE_SINT, + SG_IMAGESAMPLETYPE_UINT, + SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT, + _SG_IMAGESAMPLETYPE_NUM, + _SG_IMAGESAMPLETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_image_sample_type; + +/* + sg_sampler_type + + The basic type of a texture sampler (sampling vs comparison) as + defined in a shader. Must be provided in sg_shader_sampler_desc. + + sg_image_sample_type and sg_sampler_type for a texture/sampler + pair must be compatible with each other, specifically only + the following pairs are allowed: + + - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING) + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON +*/ +typedef enum sg_sampler_type { + _SG_SAMPLERTYPE_DEFAULT, + SG_SAMPLERTYPE_FILTERING, + SG_SAMPLERTYPE_NONFILTERING, + SG_SAMPLERTYPE_COMPARISON, + _SG_SAMPLERTYPE_NUM, + _SG_SAMPLERTYPE_FORCE_U32, +} sg_sampler_type; + +/* + sg_cube_face + + The cubemap faces. Use these as indices in the sg_image_desc.content + array. +*/ +typedef enum sg_cube_face { + SG_CUBEFACE_POS_X, + SG_CUBEFACE_NEG_X, + SG_CUBEFACE_POS_Y, + SG_CUBEFACE_NEG_Y, + SG_CUBEFACE_POS_Z, + SG_CUBEFACE_NEG_Z, + SG_CUBEFACE_NUM, + _SG_CUBEFACE_FORCE_U32 = 0x7FFFFFFF +} sg_cube_face; + +/* + sg_primitive_type + + This is the common subset of 3D primitive types supported across all 3D + APIs. This is used in the sg_pipeline_desc.primitive_type member when + creating a pipeline object. + + The default primitive type is SG_PRIMITIVETYPE_TRIANGLES. +*/ +typedef enum sg_primitive_type { + _SG_PRIMITIVETYPE_DEFAULT, // value 0 reserved for default-init + SG_PRIMITIVETYPE_POINTS, + SG_PRIMITIVETYPE_LINES, + SG_PRIMITIVETYPE_LINE_STRIP, + SG_PRIMITIVETYPE_TRIANGLES, + SG_PRIMITIVETYPE_TRIANGLE_STRIP, + _SG_PRIMITIVETYPE_NUM, + _SG_PRIMITIVETYPE_FORCE_U32 = 0x7FFFFFFF +} sg_primitive_type; + +/* + sg_filter + + The filtering mode when sampling a texture image. This is + used in the sg_sampler_desc.min_filter, sg_sampler_desc.mag_filter + and sg_sampler_desc.mipmap_filter members when creating a sampler object. + + For the default is SG_FILTER_NEAREST. +*/ +typedef enum sg_filter { + _SG_FILTER_DEFAULT, // value 0 reserved for default-init + SG_FILTER_NEAREST, + SG_FILTER_LINEAR, + _SG_FILTER_NUM, + _SG_FILTER_FORCE_U32 = 0x7FFFFFFF +} sg_filter; + +/* + sg_wrap + + The texture coordinates wrapping mode when sampling a texture + image. This is used in the sg_image_desc.wrap_u, .wrap_v + and .wrap_w members when creating an image. + + The default wrap mode is SG_WRAP_REPEAT. + + NOTE: SG_WRAP_CLAMP_TO_BORDER is not supported on all backends + and platforms. To check for support, call sg_query_features() + and check the "clamp_to_border" boolean in the returned + sg_features struct. + + Platforms which don't support SG_WRAP_CLAMP_TO_BORDER will silently fall back + to SG_WRAP_CLAMP_TO_EDGE without a validation error. +*/ +typedef enum sg_wrap { + _SG_WRAP_DEFAULT, // value 0 reserved for default-init + SG_WRAP_REPEAT, + SG_WRAP_CLAMP_TO_EDGE, + SG_WRAP_CLAMP_TO_BORDER, + SG_WRAP_MIRRORED_REPEAT, + _SG_WRAP_NUM, + _SG_WRAP_FORCE_U32 = 0x7FFFFFFF +} sg_wrap; + +/* + sg_border_color + + The border color to use when sampling a texture, and the UV wrap + mode is SG_WRAP_CLAMP_TO_BORDER. + + The default border color is SG_BORDERCOLOR_OPAQUE_BLACK +*/ +typedef enum sg_border_color { + _SG_BORDERCOLOR_DEFAULT, // value 0 reserved for default-init + SG_BORDERCOLOR_TRANSPARENT_BLACK, + SG_BORDERCOLOR_OPAQUE_BLACK, + SG_BORDERCOLOR_OPAQUE_WHITE, + _SG_BORDERCOLOR_NUM, + _SG_BORDERCOLOR_FORCE_U32 = 0x7FFFFFFF +} sg_border_color; + +/* + sg_vertex_format + + The data type of a vertex component. This is used to describe + the layout of input vertex data when creating a pipeline object. + + NOTE that specific mapping rules exist from the CPU-side vertex + formats to the vertex attribute base type in the vertex shader code + (see doc header section 'ON VERTEX FORMATS'). +*/ +typedef enum sg_vertex_format { + SG_VERTEXFORMAT_INVALID, + SG_VERTEXFORMAT_FLOAT, + SG_VERTEXFORMAT_FLOAT2, + SG_VERTEXFORMAT_FLOAT3, + SG_VERTEXFORMAT_FLOAT4, + SG_VERTEXFORMAT_INT, + SG_VERTEXFORMAT_INT2, + SG_VERTEXFORMAT_INT3, + SG_VERTEXFORMAT_INT4, + SG_VERTEXFORMAT_UINT, + SG_VERTEXFORMAT_UINT2, + SG_VERTEXFORMAT_UINT3, + SG_VERTEXFORMAT_UINT4, + SG_VERTEXFORMAT_BYTE4, + SG_VERTEXFORMAT_BYTE4N, + SG_VERTEXFORMAT_UBYTE4, + SG_VERTEXFORMAT_UBYTE4N, + SG_VERTEXFORMAT_SHORT2, + SG_VERTEXFORMAT_SHORT2N, + SG_VERTEXFORMAT_USHORT2, + SG_VERTEXFORMAT_USHORT2N, + SG_VERTEXFORMAT_SHORT4, + SG_VERTEXFORMAT_SHORT4N, + SG_VERTEXFORMAT_USHORT4, + SG_VERTEXFORMAT_USHORT4N, + SG_VERTEXFORMAT_UINT10_N2, + SG_VERTEXFORMAT_HALF2, + SG_VERTEXFORMAT_HALF4, + _SG_VERTEXFORMAT_NUM, + _SG_VERTEXFORMAT_FORCE_U32 = 0x7FFFFFFF +} sg_vertex_format; + +/* + sg_vertex_step + + Defines whether the input pointer of a vertex input stream is advanced + 'per vertex' or 'per instance'. The default step-func is + SG_VERTEXSTEP_PER_VERTEX. SG_VERTEXSTEP_PER_INSTANCE is used with + instanced-rendering. + + The vertex-step is part of the vertex-layout definition + when creating pipeline objects. +*/ +typedef enum sg_vertex_step { + _SG_VERTEXSTEP_DEFAULT, // value 0 reserved for default-init + SG_VERTEXSTEP_PER_VERTEX, + SG_VERTEXSTEP_PER_INSTANCE, + _SG_VERTEXSTEP_NUM, + _SG_VERTEXSTEP_FORCE_U32 = 0x7FFFFFFF +} sg_vertex_step; + +/* + sg_uniform_type + + The data type of a uniform block member. This is used to + describe the internal layout of uniform blocks when creating + a shader object. This is only required for the GL backend, all + other backends will ignore the interior layout of uniform blocks. +*/ +typedef enum sg_uniform_type { + SG_UNIFORMTYPE_INVALID, + SG_UNIFORMTYPE_FLOAT, + SG_UNIFORMTYPE_FLOAT2, + SG_UNIFORMTYPE_FLOAT3, + SG_UNIFORMTYPE_FLOAT4, + SG_UNIFORMTYPE_INT, + SG_UNIFORMTYPE_INT2, + SG_UNIFORMTYPE_INT3, + SG_UNIFORMTYPE_INT4, + SG_UNIFORMTYPE_MAT4, + _SG_UNIFORMTYPE_NUM, + _SG_UNIFORMTYPE_FORCE_U32 = 0x7FFFFFFF +} sg_uniform_type; + +/* + sg_uniform_layout + + A hint for the interior memory layout of uniform blocks. This is + only relevant for the GL backend where the internal layout + of uniform blocks must be known to sokol-gfx. For all other backends the + internal memory layout of uniform blocks doesn't matter, sokol-gfx + will just pass uniform data as an opaque memory blob to the + 3D backend. + + SG_UNIFORMLAYOUT_NATIVE (default) + Native layout means that a 'backend-native' memory layout + is used. For the GL backend this means that uniforms + are packed tightly in memory (e.g. there are no padding + bytes). + + SG_UNIFORMLAYOUT_STD140 + The memory layout is a subset of std140. Arrays are only + allowed for the FLOAT4, INT4 and MAT4. Alignment is as + is as follows: + + FLOAT, INT: 4 byte alignment + FLOAT2, INT2: 8 byte alignment + FLOAT3, INT3: 16 byte alignment(!) + FLOAT4, INT4: 16 byte alignment + MAT4: 16 byte alignment + FLOAT4[], INT4[]: 16 byte alignment + + The overall size of the uniform block must be a multiple + of 16. + + For more information search for 'UNIFORM DATA LAYOUT' in the documentation block + at the start of the header. +*/ +typedef enum sg_uniform_layout { + _SG_UNIFORMLAYOUT_DEFAULT, // value 0 reserved for default-init + SG_UNIFORMLAYOUT_NATIVE, // default: layout depends on currently active backend + SG_UNIFORMLAYOUT_STD140, // std140: memory layout according to std140 + _SG_UNIFORMLAYOUT_NUM, + _SG_UNIFORMLAYOUT_FORCE_U32 = 0x7FFFFFFF +} sg_uniform_layout; + +/* + sg_cull_mode + + The face-culling mode, this is used in the + sg_pipeline_desc.cull_mode member when creating a + pipeline object. + + The default cull mode is SG_CULLMODE_NONE +*/ +typedef enum sg_cull_mode { + _SG_CULLMODE_DEFAULT, // value 0 reserved for default-init + SG_CULLMODE_NONE, + SG_CULLMODE_FRONT, + SG_CULLMODE_BACK, + _SG_CULLMODE_NUM, + _SG_CULLMODE_FORCE_U32 = 0x7FFFFFFF +} sg_cull_mode; + +/* + sg_face_winding + + The vertex-winding rule that determines a front-facing primitive. This + is used in the member sg_pipeline_desc.face_winding + when creating a pipeline object. + + The default winding is SG_FACEWINDING_CW (clockwise) +*/ +typedef enum sg_face_winding { + _SG_FACEWINDING_DEFAULT, // value 0 reserved for default-init + SG_FACEWINDING_CCW, + SG_FACEWINDING_CW, + _SG_FACEWINDING_NUM, + _SG_FACEWINDING_FORCE_U32 = 0x7FFFFFFF +} sg_face_winding; + +/* + sg_compare_func + + The compare-function for configuring depth- and stencil-ref tests + in pipeline objects, and for texture samplers which perform a comparison + instead of regular sampling operation. + + Used in the following structs: + + sg_pipeline_desc + .depth + .compare + .stencil + .front.compare + .back.compare + + sg_sampler_desc + .compare + + The default compare func for depth- and stencil-tests is + SG_COMPAREFUNC_ALWAYS. + + The default compare func for samplers is SG_COMPAREFUNC_NEVER. +*/ +typedef enum sg_compare_func { + _SG_COMPAREFUNC_DEFAULT, // value 0 reserved for default-init + SG_COMPAREFUNC_NEVER, + SG_COMPAREFUNC_LESS, + SG_COMPAREFUNC_EQUAL, + SG_COMPAREFUNC_LESS_EQUAL, + SG_COMPAREFUNC_GREATER, + SG_COMPAREFUNC_NOT_EQUAL, + SG_COMPAREFUNC_GREATER_EQUAL, + SG_COMPAREFUNC_ALWAYS, + _SG_COMPAREFUNC_NUM, + _SG_COMPAREFUNC_FORCE_U32 = 0x7FFFFFFF +} sg_compare_func; + +/* + sg_stencil_op + + The operation performed on a currently stored stencil-value when a + comparison test passes or fails. This is used when creating a pipeline + object in the following sg_pipeline_desc struct items: + + sg_pipeline_desc + .stencil + .front + .fail_op + .depth_fail_op + .pass_op + .back + .fail_op + .depth_fail_op + .pass_op + + The default value is SG_STENCILOP_KEEP. +*/ +typedef enum sg_stencil_op { + _SG_STENCILOP_DEFAULT, // value 0 reserved for default-init + SG_STENCILOP_KEEP, + SG_STENCILOP_ZERO, + SG_STENCILOP_REPLACE, + SG_STENCILOP_INCR_CLAMP, + SG_STENCILOP_DECR_CLAMP, + SG_STENCILOP_INVERT, + SG_STENCILOP_INCR_WRAP, + SG_STENCILOP_DECR_WRAP, + _SG_STENCILOP_NUM, + _SG_STENCILOP_FORCE_U32 = 0x7FFFFFFF +} sg_stencil_op; + +/* + sg_blend_factor + + The source and destination factors in blending operations. + This is used in the following members when creating a pipeline object: + + sg_pipeline_desc + .colors[i] + .blend + .src_factor_rgb + .dst_factor_rgb + .src_factor_alpha + .dst_factor_alpha + + The default value is SG_BLENDFACTOR_ONE for source + factors, and for the destination SG_BLENDFACTOR_ZERO if the associated + blend-op is ADD, SUBTRACT or REVERSE_SUBTRACT or SG_BLENDFACTOR_ONE + if the associated blend-op is MIN or MAX. +*/ +typedef enum sg_blend_factor { + _SG_BLENDFACTOR_DEFAULT, // value 0 reserved for default-init + SG_BLENDFACTOR_ZERO, + SG_BLENDFACTOR_ONE, + SG_BLENDFACTOR_SRC_COLOR, + SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR, + SG_BLENDFACTOR_SRC_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + SG_BLENDFACTOR_DST_COLOR, + SG_BLENDFACTOR_ONE_MINUS_DST_COLOR, + SG_BLENDFACTOR_DST_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA, + SG_BLENDFACTOR_SRC_ALPHA_SATURATED, + SG_BLENDFACTOR_BLEND_COLOR, + SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR, + SG_BLENDFACTOR_BLEND_ALPHA, + SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA, + _SG_BLENDFACTOR_NUM, + _SG_BLENDFACTOR_FORCE_U32 = 0x7FFFFFFF +} sg_blend_factor; + +/* + sg_blend_op + + Describes how the source and destination values are combined in the + fragment blending operation. It is used in the following struct items + when creating a pipeline object: + + sg_pipeline_desc + .colors[i] + .blend + .op_rgb + .op_alpha + + The default value is SG_BLENDOP_ADD. +*/ +typedef enum sg_blend_op { + _SG_BLENDOP_DEFAULT, // value 0 reserved for default-init + SG_BLENDOP_ADD, + SG_BLENDOP_SUBTRACT, + SG_BLENDOP_REVERSE_SUBTRACT, + SG_BLENDOP_MIN, + SG_BLENDOP_MAX, + _SG_BLENDOP_NUM, + _SG_BLENDOP_FORCE_U32 = 0x7FFFFFFF +} sg_blend_op; + +/* + sg_color_mask + + Selects the active color channels when writing a fragment color to the + framebuffer. This is used in the members + sg_pipeline_desc.colors[i].write_mask when creating a pipeline object. + + The default colormask is SG_COLORMASK_RGBA (write all colors channels) + + NOTE: since the color mask value 0 is reserved for the default value + (SG_COLORMASK_RGBA), use SG_COLORMASK_NONE if all color channels + should be disabled. +*/ +typedef enum sg_color_mask { + _SG_COLORMASK_DEFAULT = 0, // value 0 reserved for default-init + SG_COLORMASK_NONE = 0x10, // special value for 'all channels disabled + SG_COLORMASK_R = 0x1, + SG_COLORMASK_G = 0x2, + SG_COLORMASK_RG = 0x3, + SG_COLORMASK_B = 0x4, + SG_COLORMASK_RB = 0x5, + SG_COLORMASK_GB = 0x6, + SG_COLORMASK_RGB = 0x7, + SG_COLORMASK_A = 0x8, + SG_COLORMASK_RA = 0x9, + SG_COLORMASK_GA = 0xA, + SG_COLORMASK_RGA = 0xB, + SG_COLORMASK_BA = 0xC, + SG_COLORMASK_RBA = 0xD, + SG_COLORMASK_GBA = 0xE, + SG_COLORMASK_RGBA = 0xF, + _SG_COLORMASK_FORCE_U32 = 0x7FFFFFFF +} sg_color_mask; + +/* + sg_load_action + + Defines the load action that should be performed at the start of a render pass: + + SG_LOADACTION_CLEAR: clear the render target + SG_LOADACTION_LOAD: load the previous content of the render target + SG_LOADACTION_DONTCARE: leave the render target in an undefined state + + This is used in the sg_pass_action structure. + + The default load action for all pass attachments is SG_LOADACTION_CLEAR, + with the values rgba = { 0.5f, 0.5f, 0.5f, 1.0f }, depth=1.0f and stencil=0. + + If you want to override the default behaviour, it is important to not + only set the clear color, but the 'action' field as well (as long as this + is _SG_LOADACTION_DEFAULT, the value fields will be ignored). +*/ +typedef enum sg_load_action { + _SG_LOADACTION_DEFAULT, + SG_LOADACTION_CLEAR, + SG_LOADACTION_LOAD, + SG_LOADACTION_DONTCARE, + _SG_LOADACTION_FORCE_U32 = 0x7FFFFFFF +} sg_load_action; + +/* + sg_store_action + + Defines the store action that should be performed at the end of a render pass: + + SG_STOREACTION_STORE: store the rendered content to the color attachment image + SG_STOREACTION_DONTCARE: allows the GPU to discard the rendered content +*/ +typedef enum sg_store_action { + _SG_STOREACTION_DEFAULT, + SG_STOREACTION_STORE, + SG_STOREACTION_DONTCARE, + _SG_STOREACTION_FORCE_U32 = 0x7FFFFFFF +} sg_store_action; + + +/* + sg_pass_action + + The sg_pass_action struct defines the actions to be performed + at the start and end of a render pass. + + - at the start of the pass: whether the render attachments should be cleared, + loaded with their previous content, or start in an undefined state + - for clear operations: the clear value (color, depth, or stencil values) + - at the end of the pass: whether the rendering result should be + stored back into the render attachment or discarded +*/ +typedef struct sg_color_attachment_action { + sg_load_action load_action; // default: SG_LOADACTION_CLEAR + sg_store_action store_action; // default: SG_STOREACTION_STORE + sg_color clear_value; // default: { 0.5f, 0.5f, 0.5f, 1.0f } +} sg_color_attachment_action; + +typedef struct sg_depth_attachment_action { + sg_load_action load_action; // default: SG_LOADACTION_CLEAR + sg_store_action store_action; // default: SG_STOREACTION_DONTCARE + float clear_value; // default: 1.0 +} sg_depth_attachment_action; + +typedef struct sg_stencil_attachment_action { + sg_load_action load_action; // default: SG_LOADACTION_CLEAR + sg_store_action store_action; // default: SG_STOREACTION_DONTCARE + uint8_t clear_value; // default: 0 +} sg_stencil_attachment_action; + +typedef struct sg_pass_action { + sg_color_attachment_action colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_depth_attachment_action depth; + sg_stencil_attachment_action stencil; +} sg_pass_action; + +/* + sg_swapchain + + Used in sg_begin_pass() to provide details about an external swapchain + (pixel formats, sample count and backend-API specific render surface objects). + + The following information must be provided: + + - the width and height of the swapchain surfaces in number of pixels, + - the pixel format of the render- and optional msaa-resolve-surface + - the pixel format of the optional depth- or depth-stencil-surface + - the MSAA sample count for the render and depth-stencil surface + + If the pixel formats and MSAA sample counts are left zero-initialized, + their defaults are taken from the sg_environment struct provided in the + sg_setup() call. + + The width and height *must* be > 0. + + Additionally the following backend API specific objects must be passed in + as 'type erased' void pointers: + + GL: + - on all GL backends, a GL framebuffer object must be provided. This + can be zero for the default framebuffer. + + D3D11: + - an ID3D11RenderTargetView for the rendering surface, without + MSAA rendering this surface will also be displayed + - an optional ID3D11DepthStencilView for the depth- or depth/stencil + buffer surface + - when MSAA rendering is used, another ID3D11RenderTargetView + which serves as MSAA resolve target and will be displayed + + WebGPU (same as D3D11, except different types) + - a WGPUTextureView for the rendering surface, without + MSAA rendering this surface will also be displayed + - an optional WGPUTextureView for the depth- or depth/stencil + buffer surface + - when MSAA rendering is used, another WGPUTextureView + which serves as MSAA resolve target and will be displayed + + Metal (NOTE that the roles of provided surfaces is slightly different + than on D3D11 or WebGPU in case of MSAA vs non-MSAA rendering): + + - A current CAMetalDrawable (NOT an MTLDrawable!) which will be presented. + This will either be rendered to directly (if no MSAA is used), or serve + as MSAA-resolve target. + - an optional MTLTexture for the depth- or depth-stencil buffer + - an optional multisampled MTLTexture which serves as intermediate + rendering surface which will then be resolved into the + CAMetalDrawable. + + NOTE that for Metal you must use an ObjC __bridge cast to + properly tunnel the ObjC object id through a C void*, e.g.: + + swapchain.metal.current_drawable = (__bridge const void*) [mtkView currentDrawable]; + + On all other backends you shouldn't need to mess with the reference count. + + It's a good practice to write a helper function which returns an initialized + sg_swapchain structs, which can then be plugged directly into + sg_pass.swapchain. Look at the function sglue_swapchain() in the sokol_glue.h + as an example. +*/ +typedef struct sg_metal_swapchain { + const void* current_drawable; // CAMetalDrawable (NOT MTLDrawable!!!) + const void* depth_stencil_texture; // MTLTexture + const void* msaa_color_texture; // MTLTexture +} sg_metal_swapchain; + +typedef struct sg_d3d11_swapchain { + const void* render_view; // ID3D11RenderTargetView + const void* resolve_view; // ID3D11RenderTargetView + const void* depth_stencil_view; // ID3D11DepthStencilView +} sg_d3d11_swapchain; + +typedef struct sg_wgpu_swapchain { + const void* render_view; // WGPUTextureView + const void* resolve_view; // WGPUTextureView + const void* depth_stencil_view; // WGPUTextureView +} sg_wgpu_swapchain; + +typedef struct sg_gl_swapchain { + uint32_t framebuffer; // GL framebuffer object +} sg_gl_swapchain; + +typedef struct sg_swapchain { + int width; + int height; + int sample_count; + sg_pixel_format color_format; + sg_pixel_format depth_format; + sg_metal_swapchain metal; + sg_d3d11_swapchain d3d11; + sg_wgpu_swapchain wgpu; + sg_gl_swapchain gl; +} sg_swapchain; + +/* + sg_pass + + The sg_pass structure is passed as argument into the sg_begin_pass() + function. + + For a swapchain render pass, provide an sg_pass_action and sg_swapchain + struct (for instance via the sglue_swapchain() helper function from + sokol_glue.h): + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .swapchain = sglue_swapchain(), + }); + + For an offscreen render pass, provide an sg_pass_action struct and + an sg_attachments handle: + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .attachments = attachments, + }); + + You can also omit the .action object to get default pass action behaviour + (clear to color=grey, depth=1 and stencil=0). + + For a compute pass, just set the sg_pass.compute boolean to true: + + sg_begin_pass(&(sg_pass){ .compute = true }); +*/ +typedef struct sg_pass { + uint32_t _start_canary; + bool compute; + sg_pass_action action; + sg_attachments attachments; + sg_swapchain swapchain; + const char* label; + uint32_t _end_canary; +} sg_pass; + +/* + sg_bindings + + The sg_bindings structure defines the buffers, images and + samplers resource bindings for the next draw call. + + To update the resource bindings, call sg_apply_bindings() with + a pointer to a populated sg_bindings struct. Note that + sg_apply_bindings() must be called after sg_apply_pipeline() + and that bindings are not preserved across sg_apply_pipeline() + calls, even when the new pipeline uses the same 'bindings layout'. + + A resource binding struct contains: + + - 1..N vertex buffers + - 0..N vertex buffer offsets + - 0..1 index buffers + - 0..1 index buffer offsets + - 0..N images + - 0..N samplers + - 0..N storage buffers + + Where 'N' is defined in the following constants: + + - SG_MAX_VERTEXBUFFER_BINDSLOTS + - SG_MAX_IMAGE_BINDLOTS + - SG_MAX_SAMPLER_BINDSLOTS + - SG_MAX_STORAGEBUFFER_BINDGLOTS + + Note that inside compute passes vertex- and index-buffer-bindings are + disallowed. + + When using sokol-shdc for shader authoring, the `layout(binding=N)` + annotation in the shader code directly maps to the slot index for that + resource type in the bindings struct, for instance the following vertex- + and fragment-shader interface for sokol-shdc: + + @vs vs + layout(binding=0) uniform vs_params { ... }; + layout(binding=0) readonly buffer ssbo { ... }; + layout(binding=0) uniform texture2D vs_tex; + layout(binding=0) uniform sampler vs_smp; + ... + @end + + @fs fs + layout(binding=1) uniform fs_params { ... }; + layout(binding=1) uniform texture2D fs_tex; + layout(binding=1) uniform sampler fs_smp; + ... + @end + + ...would map to the following sg_bindings struct: + + const sg_bindings bnd = { + .vertex_buffers[0] = ..., + .images[0] = vs_tex, + .images[1] = fs_tex, + .samplers[0] = vs_smp, + .samplers[1] = fs_smp, + .storage_buffers[0] = ssbo, + }; + + ...alternatively you can use code-generated slot indices: + + const sg_bindings bnd = { + .vertex_buffers[0] = ..., + .images[IMG_vs_tex] = vs_tex, + .images[IMG_fs_tex] = fs_tex, + .samplers[SMP_vs_smp] = vs_smp, + .samplers[SMP_fs_smp] = fs_smp, + .storage_buffers[SBUF_ssbo] = ssbo, + }; + + Resource bindslots for a specific shader/pipeline may have gaps, and an + sg_bindings struct may have populated bind slots which are not used by a + specific shader. This allows to use the same sg_bindings struct across + different shader variants. + + When not using sokol-shdc, the bindslot indices in the sg_bindings + struct need to match the per-resource reflection info slot indices + in the sg_shader_desc struct (for details about that see the + sg_shader_desc struct documentation). + + The optional buffer offsets can be used to put different unrelated + chunks of vertex- and/or index-data into the same buffer objects. +*/ +typedef struct sg_bindings { + uint32_t _start_canary; + sg_buffer vertex_buffers[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + int vertex_buffer_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + sg_buffer index_buffer; + int index_buffer_offset; + sg_image images[SG_MAX_IMAGE_BINDSLOTS]; + sg_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS]; + sg_buffer storage_buffers[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + uint32_t _end_canary; +} sg_bindings; + +/* + sg_buffer_desc + + Creation parameters for sg_buffer objects, used in the + sg_make_buffer() call. + + The default configuration is: + + .size: 0 (*must* be >0 for buffers without data) + .type: SG_BUFFERTYPE_VERTEXBUFFER + .usage: SG_USAGE_IMMUTABLE + .data.ptr 0 (*must* be valid for immutable buffers) + .data.size 0 (*must* be > 0 for immutable buffers) + .label 0 (optional string label) + + For immutable buffers which are initialized with initial data, + keep the .size item zero-initialized, and set the size together with the + pointer to the initial data in the .data item. + + For immutable or mutable buffers without initial data, keep the .data item + zero-initialized, and set the buffer size in the .size item instead. + + NOTE: Immutable buffers without initial data are guaranteed to be + zero-initialized. For mutable (dynamic or streaming) buffers, the + initial content is undefined. + + You can also set both size values, but currently both size values must + be identical (this may change in the future when the dynamic resource + management may become more flexible). + + ADVANCED TOPIC: Injecting native 3D-API buffers: + + The following struct members allow to inject your own GL, Metal + or D3D11 buffers into sokol_gfx: + + .gl_buffers[SG_NUM_INFLIGHT_FRAMES] + .mtl_buffers[SG_NUM_INFLIGHT_FRAMES] + .d3d11_buffer + + You must still provide all other struct items except the .data item, and + these must match the creation parameters of the native buffers you + provide. For SG_USAGE_IMMUTABLE, only provide a single native 3D-API + buffer, otherwise you need to provide SG_NUM_INFLIGHT_FRAMES buffers + (only for GL and Metal, not D3D11). Providing multiple buffers for GL and + Metal is necessary because sokol_gfx will rotate through them when + calling sg_update_buffer() to prevent lock-stalls. + + Note that it is expected that immutable injected buffer have already been + initialized with content, and the .content member must be 0! + + Also you need to call sg_reset_state_cache() after calling native 3D-API + functions, and before calling any sokol_gfx function. +*/ +typedef struct sg_buffer_desc { + uint32_t _start_canary; + size_t size; + sg_buffer_type type; + sg_usage usage; + sg_range data; + const char* label; + // optionally inject backend-specific resources + uint32_t gl_buffers[SG_NUM_INFLIGHT_FRAMES]; + const void* mtl_buffers[SG_NUM_INFLIGHT_FRAMES]; + const void* d3d11_buffer; + const void* wgpu_buffer; + uint32_t _end_canary; +} sg_buffer_desc; + +/* + sg_image_data + + Defines the content of an image through a 2D array of sg_range structs. + The first array dimension is the cubemap face, and the second array + dimension the mipmap level. +*/ +typedef struct sg_image_data { + sg_range subimage[SG_CUBEFACE_NUM][SG_MAX_MIPMAPS]; +} sg_image_data; + +/* + sg_image_desc + + Creation parameters for sg_image objects, used in the sg_make_image() call. + + The default configuration is: + + .type: SG_IMAGETYPE_2D + .render_target: false + .width 0 (must be set to >0) + .height 0 (must be set to >0) + .num_slices 1 (3D textures: depth; array textures: number of layers) + .num_mipmaps: 1 + .usage: SG_USAGE_IMMUTABLE + .pixel_format: SG_PIXELFORMAT_RGBA8 for textures, or sg_desc.environment.defaults.color_format for render targets + .sample_count: 1 for textures, or sg_desc.environment.defaults.sample_count for render targets + .data an sg_image_data struct to define the initial content + .label 0 (optional string label for trace hooks) + + Q: Why is the default sample_count for render targets identical with the + "default sample count" from sg_desc.environment.defaults.sample_count? + + A: So that it matches the default sample count in pipeline objects. Even + though it is a bit strange/confusing that offscreen render targets by default + get the same sample count as 'default swapchains', but it's better that + an offscreen render target created with default parameters matches + a pipeline object created with default parameters. + + NOTE: + + Images with usage SG_USAGE_IMMUTABLE must be fully initialized by + providing a valid .data member which points to initialization data. + + ADVANCED TOPIC: Injecting native 3D-API textures: + + The following struct members allow to inject your own GL, Metal or D3D11 + textures into sokol_gfx: + + .gl_textures[SG_NUM_INFLIGHT_FRAMES] + .mtl_textures[SG_NUM_INFLIGHT_FRAMES] + .d3d11_texture + .d3d11_shader_resource_view + .wgpu_texture + .wgpu_texture_view + + For GL, you can also specify the texture target or leave it empty to use + the default texture target for the image type (GL_TEXTURE_2D for + SG_IMAGETYPE_2D etc) + + For D3D11 and WebGPU, either only provide a texture, or both a texture and + shader-resource-view / texture-view object. If you want to use access the + injected texture in a shader you *must* provide a shader-resource-view. + + The same rules apply as for injecting native buffers (see sg_buffer_desc + documentation for more details). +*/ +typedef struct sg_image_desc { + uint32_t _start_canary; + sg_image_type type; + bool render_target; + int width; + int height; + int num_slices; + int num_mipmaps; + sg_usage usage; + sg_pixel_format pixel_format; + int sample_count; + sg_image_data data; + const char* label; + // optionally inject backend-specific resources + uint32_t gl_textures[SG_NUM_INFLIGHT_FRAMES]; + uint32_t gl_texture_target; + const void* mtl_textures[SG_NUM_INFLIGHT_FRAMES]; + const void* d3d11_texture; + const void* d3d11_shader_resource_view; + const void* wgpu_texture; + const void* wgpu_texture_view; + uint32_t _end_canary; +} sg_image_desc; + +/* + sg_sampler_desc + + Creation parameters for sg_sampler objects, used in the sg_make_sampler() call + + .min_filter: SG_FILTER_NEAREST + .mag_filter: SG_FILTER_NEAREST + .mipmap_filter SG_FILTER_NEAREST + .wrap_u: SG_WRAP_REPEAT + .wrap_v: SG_WRAP_REPEAT + .wrap_w: SG_WRAP_REPEAT (only SG_IMAGETYPE_3D) + .min_lod 0.0f + .max_lod FLT_MAX + .border_color SG_BORDERCOLOR_OPAQUE_BLACK + .compare SG_COMPAREFUNC_NEVER + .max_anisotropy 1 (must be 1..16) + +*/ +typedef struct sg_sampler_desc { + uint32_t _start_canary; + sg_filter min_filter; + sg_filter mag_filter; + sg_filter mipmap_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + sg_wrap wrap_w; + float min_lod; + float max_lod; + sg_border_color border_color; + sg_compare_func compare; + uint32_t max_anisotropy; + const char* label; + // optionally inject backend-specific resources + uint32_t gl_sampler; + const void* mtl_sampler; + const void* d3d11_sampler; + const void* wgpu_sampler; + uint32_t _end_canary; +} sg_sampler_desc; + +/* + sg_shader_desc + + Used as parameter of sg_make_shader() to create a shader object which + communicates shader source or bytecode and shader interface + reflection information to sokol-gfx. + + If you use sokol-shdc you can ignore the following information since + the sg_shader_desc struct will be code generated. + + Otherwise you need to provide the following information to the + sg_make_shader() call: + + - a vertex- and fragment-shader function: + - the shader source or bytecode + - an optional entry point name + - for D3D11: an optional compile target when source code is provided + (the defaults are "vs_4_0" and "ps_4_0") + + - ...or alternatively, a compute function: + - the shader source or bytecode + - an optional entry point name + - for D3D11: an optional compile target when source code is provided + (the default is "cs_5_0") + + - vertex attributes required by some backends (not for compute shaders): + - the vertex attribute base type (undefined, float, signed int, unsigned int), + this information is only used in the validation layer to check that the + pipeline object vertex formats are compatible with the input vertex attribute + type used in the vertex shader. NOTE that the default base type + 'undefined' skips the validation layer check. + - for the GL backend: optional vertex attribute names used for name lookup + - for the D3D11 backend: semantic names and indices + + - only for compute shaders on the Metal backend: + - the workgroup size aka 'threads per thread-group' + + In other 3D APIs this is declared in the shader code: + - GLSL: `layout(local_size_x=x, local_size_y=y, local_size_y=z) in;` + - HLSL: `[numthreads(x, y, z)]` + - WGSL: `@workgroup_size(x, y, z)` + ...but in Metal the workgroup size is declared on the CPU side + + - reflection information for each uniform block used by the shader: + - the shader stage the uniform block appears in (SG_SHADERSTAGE_*) + - the size in bytes of the uniform block + - backend-specific bindslots: + - HLSL: the constant buffer register `register(b0..7)` + - MSL: the buffer attribute `[[buffer(0..7)]]` + - WGSL: the binding in `@group(0) @binding(0..15)` + - GLSL only: a description of the uniform block interior + - the memory layout standard (SG_UNIFORMLAYOUT_*) + - for each member in the uniform block: + - the member type (SG_UNIFORM_*) + - if the member is an array, the array count + - the member name + + - reflection information for each texture used by the shader: + - the shader stage the texture appears in (SG_SHADERSTAGE_*) + - the image type (SG_IMAGETYPE_*) + - the image-sample type (SG_IMAGESAMPLETYPE_*) + - whether the texture is multisampled + - backend specific bindslots: + - HLSL: the texture register `register(t0..23)` + - MSL: the texture attribute `[[texture(0..15)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + + - reflection information for each sampler used by the shader: + - the shader stage the sampler appears in (SG_SHADERSTAGE_*) + - the sampler type (SG_SAMPLERTYPE_*) + - backend specific bindslots: + - HLSL: the sampler register `register(s0..15)` + - MSL: the sampler attribute `[[sampler(0..15)]]` + - WGSL: the binding in `@group(0) @binding(0..127)` + + - reflection information for each storage buffer used by the shader: + - the shader stage the storage buffer appears in (SG_SHADERSTAGE_*) + - whether the storage buffer is readonly (currently this must + always be true) + - backend specific bindslots: + - HLSL: + - for readonly storage buffer bindings: `register(t0..23)` + - for read/write storage buffer bindings: `register(u0..7)` + - MSL: the buffer attribute `[[buffer(8..15)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + - GL: the binding in `layout(binding=0..7)` + + - reflection information for each combined image-sampler object + used by the shader: + - the shader stage (SG_SHADERSTAGE_*) + - the texture's array index in the sg_shader_desc.images[] array + - the sampler's array index in the sg_shader_desc.samplers[] array + - GLSL only: the name of the combined image-sampler object + + The number and order of items in the sg_shader_desc.attrs[] + array corresponds to the items in sg_pipeline_desc.layout.attrs. + + - sg_shader_desc.attrs[N] => sg_pipeline_desc.layout.attrs[N] + + NOTE that vertex attribute indices currently cannot have gaps. + + The items index in the sg_shader_desc.uniform_blocks[] array corresponds + to the ub_slot arg in sg_apply_uniforms(): + + - sg_shader_desc.uniform_blocks[N] => sg_apply_uniforms(N, ...) + + The items in the shader_desc images, samplers and storage_buffers + arrays correspond to the same array items in the sg_bindings struct: + + - sg_shader_desc.images[N] => sg_bindings.images[N] + - sg_shader_desc.samplers[N] => sg_bindings.samplers[N] + - sg_shader_desc.storage_buffers[N] => sg_bindings.storage_buffers[N] + + For all GL backends, shader source-code must be provided. For D3D11 and Metal, + either shader source-code or byte-code can be provided. + + NOTE that the uniform block, image, sampler and storage_buffer arrays + can have gaps. This allows to use the same sg_bindings struct for + different related shader variants. + + For D3D11, if source code is provided, the d3dcompiler_47.dll will be loaded + on demand. If this fails, shader creation will fail. When compiling HLSL + source code, you can provide an optional target string via + sg_shader_stage_desc.d3d11_target, the default target is "vs_4_0" for the + vertex shader stage and "ps_4_0" for the pixel shader stage. +*/ +typedef enum sg_shader_stage { + SG_SHADERSTAGE_NONE, + SG_SHADERSTAGE_VERTEX, + SG_SHADERSTAGE_FRAGMENT, + SG_SHADERSTAGE_COMPUTE, + _SG_SHADERSTAGE_FORCE_U32 = 0x7FFFFFFF, +} sg_shader_stage; + +typedef struct sg_shader_function { + const char* source; + sg_range bytecode; + const char* entry; + const char* d3d11_target; // default: "vs_4_0" or "ps_4_0" +} sg_shader_function; + +typedef enum sg_shader_attr_base_type { + SG_SHADERATTRBASETYPE_UNDEFINED, + SG_SHADERATTRBASETYPE_FLOAT, + SG_SHADERATTRBASETYPE_SINT, + SG_SHADERATTRBASETYPE_UINT, + _SG_SHADERATTRBASETYPE_FORCE_U32 = 0x7FFFFFFF, +} sg_shader_attr_base_type; + +typedef struct sg_shader_vertex_attr { + sg_shader_attr_base_type base_type; // default: UNDEFINED (disables validation) + const char* glsl_name; // [optional] GLSL attribute name + const char* hlsl_sem_name; // HLSL semantic name + uint8_t hlsl_sem_index; // HLSL semantic index +} sg_shader_vertex_attr; + +typedef struct sg_glsl_shader_uniform { + sg_uniform_type type; + uint16_t array_count; // 0 or 1 for scalars, >1 for arrays + const char* glsl_name; // glsl name binding is required on GL 4.1 and WebGL2 +} sg_glsl_shader_uniform; + +typedef struct sg_shader_uniform_block { + sg_shader_stage stage; + uint32_t size; + uint8_t hlsl_register_b_n; // HLSL register(bn) + uint8_t msl_buffer_n; // MSL [[buffer(n)]] + uint8_t wgsl_group0_binding_n; // WGSL @group(0) @binding(n) + sg_uniform_layout layout; + sg_glsl_shader_uniform glsl_uniforms[SG_MAX_UNIFORMBLOCK_MEMBERS]; +} sg_shader_uniform_block; + +typedef struct sg_shader_image { + sg_shader_stage stage; + sg_image_type image_type; + sg_image_sample_type sample_type; + bool multisampled; + uint8_t hlsl_register_t_n; // HLSL register(tn) bind slot + uint8_t msl_texture_n; // MSL [[texture(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(1) @binding(n) bind slot +} sg_shader_image; + +typedef struct sg_shader_sampler { + sg_shader_stage stage; + sg_sampler_type sampler_type; + uint8_t hlsl_register_s_n; // HLSL register(sn) bind slot + uint8_t msl_sampler_n; // MSL [[sampler(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(1) @binding(n) bind slot +} sg_shader_sampler; + +typedef struct sg_shader_storage_buffer { + sg_shader_stage stage; + bool readonly; + uint8_t hlsl_register_t_n; // HLSL register(tn) bind slot (for readonly access) + uint8_t hlsl_register_u_n; // HLSL register(un) bind slot (for read/write access) + uint8_t msl_buffer_n; // MSL [[buffer(n)]] bind slot + uint8_t wgsl_group1_binding_n; // WGSL @group(1) @binding(n) bind slot + uint8_t glsl_binding_n; // GLSL layout(binding=n) +} sg_shader_storage_buffer; + +typedef struct sg_shader_image_sampler_pair { + sg_shader_stage stage; + uint8_t image_slot; + uint8_t sampler_slot; + const char* glsl_name; // glsl name binding required because of GL 4.1 and WebGL2 +} sg_shader_image_sampler_pair; + +typedef struct sg_mtl_shader_threads_per_threadgroup { + int x, y, z; +} sg_mtl_shader_threads_per_threadgroup; + +typedef struct sg_shader_desc { + uint32_t _start_canary; + sg_shader_function vertex_func; + sg_shader_function fragment_func; + sg_shader_function compute_func; + sg_shader_vertex_attr attrs[SG_MAX_VERTEX_ATTRIBUTES]; + sg_shader_uniform_block uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + sg_shader_storage_buffer storage_buffers[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + sg_shader_image images[SG_MAX_IMAGE_BINDSLOTS]; + sg_shader_sampler samplers[SG_MAX_SAMPLER_BINDSLOTS]; + sg_shader_image_sampler_pair image_sampler_pairs[SG_MAX_IMAGE_SAMPLER_PAIRS]; + sg_mtl_shader_threads_per_threadgroup mtl_threads_per_threadgroup; + const char* label; + uint32_t _end_canary; +} sg_shader_desc; + +/* + sg_pipeline_desc + + The sg_pipeline_desc struct defines all creation parameters for an + sg_pipeline object, used as argument to the sg_make_pipeline() function: + + Pipeline objects come in two flavours: + + - render pipelines for use in render passes + - compute pipelines for use in compute passes + + A compute pipeline only requires a compute shader object but no + 'render state', while a render pipeline requires a vertex/fragment shader + object and additional render state declarations: + + - the vertex layout for all input vertex buffers + - a shader object + - the 3D primitive type (points, lines, triangles, ...) + - the index type (none, 16- or 32-bit) + - all the fixed-function-pipeline state (depth-, stencil-, blend-state, etc...) + + If the vertex data has no gaps between vertex components, you can omit + the .layout.buffers[].stride and layout.attrs[].offset items (leave them + default-initialized to 0), sokol-gfx will then compute the offsets and + strides from the vertex component formats (.layout.attrs[].format). + Please note that ALL vertex attribute offsets must be 0 in order for the + automatic offset computation to kick in. + + The default configuration is as follows: + + .compute: false (must be set to true for a compute pipeline) + .shader: 0 (must be initialized with a valid sg_shader id!) + .layout: + .buffers[]: vertex buffer layouts + .stride: 0 (if no stride is given it will be computed) + .step_func SG_VERTEXSTEP_PER_VERTEX + .step_rate 1 + .attrs[]: vertex attribute declarations + .buffer_index 0 the vertex buffer bind slot + .offset 0 (offsets can be omitted if the vertex layout has no gaps) + .format SG_VERTEXFORMAT_INVALID (must be initialized!) + .depth: + .pixel_format: sg_desc.context.depth_format + .compare: SG_COMPAREFUNC_ALWAYS + .write_enabled: false + .bias: 0.0f + .bias_slope_scale: 0.0f + .bias_clamp: 0.0f + .stencil: + .enabled: false + .front/back: + .compare: SG_COMPAREFUNC_ALWAYS + .fail_op: SG_STENCILOP_KEEP + .depth_fail_op: SG_STENCILOP_KEEP + .pass_op: SG_STENCILOP_KEEP + .read_mask: 0 + .write_mask: 0 + .ref: 0 + .color_count 1 + .colors[0..color_count] + .pixel_format sg_desc.context.color_format + .write_mask: SG_COLORMASK_RGBA + .blend: + .enabled: false + .src_factor_rgb: SG_BLENDFACTOR_ONE + .dst_factor_rgb: SG_BLENDFACTOR_ZERO + .op_rgb: SG_BLENDOP_ADD + .src_factor_alpha: SG_BLENDFACTOR_ONE + .dst_factor_alpha: SG_BLENDFACTOR_ZERO + .op_alpha: SG_BLENDOP_ADD + .primitive_type: SG_PRIMITIVETYPE_TRIANGLES + .index_type: SG_INDEXTYPE_NONE + .cull_mode: SG_CULLMODE_NONE + .face_winding: SG_FACEWINDING_CW + .sample_count: sg_desc.context.sample_count + .blend_color: (sg_color) { 0.0f, 0.0f, 0.0f, 0.0f } + .alpha_to_coverage_enabled: false + .label 0 (optional string label for trace hooks) +*/ +typedef struct sg_vertex_buffer_layout_state { + int stride; + sg_vertex_step step_func; + int step_rate; +} sg_vertex_buffer_layout_state; + +typedef struct sg_vertex_attr_state { + int buffer_index; + int offset; + sg_vertex_format format; +} sg_vertex_attr_state; + +typedef struct sg_vertex_layout_state { + sg_vertex_buffer_layout_state buffers[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + sg_vertex_attr_state attrs[SG_MAX_VERTEX_ATTRIBUTES]; +} sg_vertex_layout_state; + +typedef struct sg_stencil_face_state { + sg_compare_func compare; + sg_stencil_op fail_op; + sg_stencil_op depth_fail_op; + sg_stencil_op pass_op; +} sg_stencil_face_state; + +typedef struct sg_stencil_state { + bool enabled; + sg_stencil_face_state front; + sg_stencil_face_state back; + uint8_t read_mask; + uint8_t write_mask; + uint8_t ref; +} sg_stencil_state; + +typedef struct sg_depth_state { + sg_pixel_format pixel_format; + sg_compare_func compare; + bool write_enabled; + float bias; + float bias_slope_scale; + float bias_clamp; +} sg_depth_state; + +typedef struct sg_blend_state { + bool enabled; + sg_blend_factor src_factor_rgb; + sg_blend_factor dst_factor_rgb; + sg_blend_op op_rgb; + sg_blend_factor src_factor_alpha; + sg_blend_factor dst_factor_alpha; + sg_blend_op op_alpha; +} sg_blend_state; + +typedef struct sg_color_target_state { + sg_pixel_format pixel_format; + sg_color_mask write_mask; + sg_blend_state blend; +} sg_color_target_state; + +typedef struct sg_pipeline_desc { + uint32_t _start_canary; + bool compute; + sg_shader shader; + sg_vertex_layout_state layout; + sg_depth_state depth; + sg_stencil_state stencil; + int color_count; + sg_color_target_state colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_primitive_type primitive_type; + sg_index_type index_type; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + int sample_count; + sg_color blend_color; + bool alpha_to_coverage_enabled; + const char* label; + uint32_t _end_canary; +} sg_pipeline_desc; + +/* + sg_attachments_desc + + Creation parameters for an sg_attachments object, used as argument to the + sg_make_attachments() function. + + An attachments object bundles 0..4 color attachments, 0..4 msaa-resolve + attachments, and none or one depth-stencil attachmente for use + in a render pass. At least one color attachment or one depth-stencil + attachment must be provided (no color attachment and a depth-stencil + attachment is useful for a depth-only render pass). + + Each attachment definition consists of an image object, and two additional indices + describing which subimage the pass will render into: one mipmap index, and if the image + is a cubemap, array-texture or 3D-texture, the face-index, array-layer or + depth-slice. + + All attachments must have the same width and height. + + All color attachments and the depth-stencil attachment must have the + same sample count. + + If a resolve attachment is set, an MSAA-resolve operation from the + associated color attachment image into the resolve attachment image will take + place in the sg_end_pass() function. In this case, the color attachment + must have a (sample_count>1), and the resolve attachment a + (sample_count==1). The resolve attachment also must have the same pixel + format as the color attachment. + + NOTE that MSAA depth-stencil attachments cannot be msaa-resolved! +*/ +typedef struct sg_attachment_desc { + sg_image image; + int mip_level; + int slice; // cube texture: face; array texture: layer; 3D texture: slice +} sg_attachment_desc; + +typedef struct sg_attachments_desc { + uint32_t _start_canary; + sg_attachment_desc colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_attachment_desc resolves[SG_MAX_COLOR_ATTACHMENTS]; + sg_attachment_desc depth_stencil; + const char* label; + uint32_t _end_canary; +} sg_attachments_desc; + +/* + sg_trace_hooks + + Installable callback functions to keep track of the sokol-gfx calls, + this is useful for debugging, or keeping track of resource creation + and destruction. + + Trace hooks are installed with sg_install_trace_hooks(), this returns + another sg_trace_hooks struct with the previous set of + trace hook function pointers. These should be invoked by the + new trace hooks to form a proper call chain. +*/ +typedef struct sg_trace_hooks { + void* user_data; + void (*reset_state_cache)(void* user_data); + void (*make_buffer)(const sg_buffer_desc* desc, sg_buffer result, void* user_data); + void (*make_image)(const sg_image_desc* desc, sg_image result, void* user_data); + void (*make_sampler)(const sg_sampler_desc* desc, sg_sampler result, void* user_data); + void (*make_shader)(const sg_shader_desc* desc, sg_shader result, void* user_data); + void (*make_pipeline)(const sg_pipeline_desc* desc, sg_pipeline result, void* user_data); + void (*make_attachments)(const sg_attachments_desc* desc, sg_attachments result, void* user_data); + void (*destroy_buffer)(sg_buffer buf, void* user_data); + void (*destroy_image)(sg_image img, void* user_data); + void (*destroy_sampler)(sg_sampler smp, void* user_data); + void (*destroy_shader)(sg_shader shd, void* user_data); + void (*destroy_pipeline)(sg_pipeline pip, void* user_data); + void (*destroy_attachments)(sg_attachments atts, void* user_data); + void (*update_buffer)(sg_buffer buf, const sg_range* data, void* user_data); + void (*update_image)(sg_image img, const sg_image_data* data, void* user_data); + void (*append_buffer)(sg_buffer buf, const sg_range* data, int result, void* user_data); + void (*begin_pass)(const sg_pass* pass, void* user_data); + void (*apply_viewport)(int x, int y, int width, int height, bool origin_top_left, void* user_data); + void (*apply_scissor_rect)(int x, int y, int width, int height, bool origin_top_left, void* user_data); + void (*apply_pipeline)(sg_pipeline pip, void* user_data); + void (*apply_bindings)(const sg_bindings* bindings, void* user_data); + void (*apply_uniforms)(int ub_index, const sg_range* data, void* user_data); + void (*draw)(int base_element, int num_elements, int num_instances, void* user_data); + void (*dispatch)(int num_groups_x, int num_groups_y, int num_groups_z, void* user_data); + void (*end_pass)(void* user_data); + void (*commit)(void* user_data); + void (*alloc_buffer)(sg_buffer result, void* user_data); + void (*alloc_image)(sg_image result, void* user_data); + void (*alloc_sampler)(sg_sampler result, void* user_data); + void (*alloc_shader)(sg_shader result, void* user_data); + void (*alloc_pipeline)(sg_pipeline result, void* user_data); + void (*alloc_attachments)(sg_attachments result, void* user_data); + void (*dealloc_buffer)(sg_buffer buf_id, void* user_data); + void (*dealloc_image)(sg_image img_id, void* user_data); + void (*dealloc_sampler)(sg_sampler smp_id, void* user_data); + void (*dealloc_shader)(sg_shader shd_id, void* user_data); + void (*dealloc_pipeline)(sg_pipeline pip_id, void* user_data); + void (*dealloc_attachments)(sg_attachments atts_id, void* user_data); + void (*init_buffer)(sg_buffer buf_id, const sg_buffer_desc* desc, void* user_data); + void (*init_image)(sg_image img_id, const sg_image_desc* desc, void* user_data); + void (*init_sampler)(sg_sampler smp_id, const sg_sampler_desc* desc, void* user_data); + void (*init_shader)(sg_shader shd_id, const sg_shader_desc* desc, void* user_data); + void (*init_pipeline)(sg_pipeline pip_id, const sg_pipeline_desc* desc, void* user_data); + void (*init_attachments)(sg_attachments atts_id, const sg_attachments_desc* desc, void* user_data); + void (*uninit_buffer)(sg_buffer buf_id, void* user_data); + void (*uninit_image)(sg_image img_id, void* user_data); + void (*uninit_sampler)(sg_sampler smp_id, void* user_data); + void (*uninit_shader)(sg_shader shd_id, void* user_data); + void (*uninit_pipeline)(sg_pipeline pip_id, void* user_data); + void (*uninit_attachments)(sg_attachments atts_id, void* user_data); + void (*fail_buffer)(sg_buffer buf_id, void* user_data); + void (*fail_image)(sg_image img_id, void* user_data); + void (*fail_sampler)(sg_sampler smp_id, void* user_data); + void (*fail_shader)(sg_shader shd_id, void* user_data); + void (*fail_pipeline)(sg_pipeline pip_id, void* user_data); + void (*fail_attachments)(sg_attachments atts_id, void* user_data); + void (*push_debug_group)(const char* name, void* user_data); + void (*pop_debug_group)(void* user_data); +} sg_trace_hooks; + +/* + sg_buffer_info + sg_image_info + sg_sampler_info + sg_shader_info + sg_pipeline_info + sg_attachments_info + + These structs contain various internal resource attributes which + might be useful for debug-inspection. Please don't rely on the + actual content of those structs too much, as they are quite closely + tied to sokol_gfx.h internals and may change more frequently than + the other public API elements. + + The *_info structs are used as the return values of the following functions: + + sg_query_buffer_info() + sg_query_image_info() + sg_query_sampler_info() + sg_query_shader_info() + sg_query_pipeline_info() + sg_query_attachments_info() +*/ +typedef struct sg_slot_info { + sg_resource_state state; // the current state of this resource slot + uint32_t res_id; // type-neutral resource if (e.g. sg_buffer.id) +} sg_slot_info; + +typedef struct sg_buffer_info { + sg_slot_info slot; // resource pool slot info + uint32_t update_frame_index; // frame index of last sg_update_buffer() + uint32_t append_frame_index; // frame index of last sg_append_buffer() + int append_pos; // current position in buffer for sg_append_buffer() + bool append_overflow; // is buffer in overflow state (due to sg_append_buffer) + int num_slots; // number of renaming-slots for dynamically updated buffers + int active_slot; // currently active write-slot for dynamically updated buffers +} sg_buffer_info; + +typedef struct sg_image_info { + sg_slot_info slot; // resource pool slot info + uint32_t upd_frame_index; // frame index of last sg_update_image() + int num_slots; // number of renaming-slots for dynamically updated images + int active_slot; // currently active write-slot for dynamically updated images +} sg_image_info; + +typedef struct sg_sampler_info { + sg_slot_info slot; // resource pool slot info +} sg_sampler_info; + +typedef struct sg_shader_info { + sg_slot_info slot; // resource pool slot info +} sg_shader_info; + +typedef struct sg_pipeline_info { + sg_slot_info slot; // resource pool slot info +} sg_pipeline_info; + +typedef struct sg_attachments_info { + sg_slot_info slot; // resource pool slot info +} sg_attachments_info; + +/* + sg_frame_stats + + Allows to track generic and backend-specific stats about a + render frame. Obtained by calling sg_query_frame_stats(). The returned + struct contains information about the *previous* frame. +*/ +typedef struct sg_frame_stats_gl { + uint32_t num_bind_buffer; + uint32_t num_active_texture; + uint32_t num_bind_texture; + uint32_t num_bind_sampler; + uint32_t num_use_program; + uint32_t num_render_state; + uint32_t num_vertex_attrib_pointer; + uint32_t num_vertex_attrib_divisor; + uint32_t num_enable_vertex_attrib_array; + uint32_t num_disable_vertex_attrib_array; + uint32_t num_uniform; + uint32_t num_memory_barriers; +} sg_frame_stats_gl; + +typedef struct sg_frame_stats_d3d11_pass { + uint32_t num_om_set_render_targets; + uint32_t num_clear_render_target_view; + uint32_t num_clear_depth_stencil_view; + uint32_t num_resolve_subresource; +} sg_frame_stats_d3d11_pass; + +typedef struct sg_frame_stats_d3d11_pipeline { + uint32_t num_rs_set_state; + uint32_t num_om_set_depth_stencil_state; + uint32_t num_om_set_blend_state; + uint32_t num_ia_set_primitive_topology; + uint32_t num_ia_set_input_layout; + uint32_t num_vs_set_shader; + uint32_t num_vs_set_constant_buffers; + uint32_t num_ps_set_shader; + uint32_t num_ps_set_constant_buffers; + uint32_t num_cs_set_shader; + uint32_t num_cs_set_constant_buffers; +} sg_frame_stats_d3d11_pipeline; + +typedef struct sg_frame_stats_d3d11_bindings { + uint32_t num_ia_set_vertex_buffers; + uint32_t num_ia_set_index_buffer; + uint32_t num_vs_set_shader_resources; + uint32_t num_vs_set_samplers; + uint32_t num_ps_set_shader_resources; + uint32_t num_ps_set_samplers; + uint32_t num_cs_set_shader_resources; + uint32_t num_cs_set_samplers; + uint32_t num_cs_set_unordered_access_views; +} sg_frame_stats_d3d11_bindings; + +typedef struct sg_frame_stats_d3d11_uniforms { + uint32_t num_update_subresource; +} sg_frame_stats_d3d11_uniforms; + +typedef struct sg_frame_stats_d3d11_draw { + uint32_t num_draw_indexed_instanced; + uint32_t num_draw_indexed; + uint32_t num_draw_instanced; + uint32_t num_draw; +} sg_frame_stats_d3d11_draw; + +typedef struct sg_frame_stats_d3d11 { + sg_frame_stats_d3d11_pass pass; + sg_frame_stats_d3d11_pipeline pipeline; + sg_frame_stats_d3d11_bindings bindings; + sg_frame_stats_d3d11_uniforms uniforms; + sg_frame_stats_d3d11_draw draw; + uint32_t num_map; + uint32_t num_unmap; +} sg_frame_stats_d3d11; + +typedef struct sg_frame_stats_metal_idpool { + uint32_t num_added; + uint32_t num_released; + uint32_t num_garbage_collected; +} sg_frame_stats_metal_idpool; + +typedef struct sg_frame_stats_metal_pipeline { + uint32_t num_set_blend_color; + uint32_t num_set_cull_mode; + uint32_t num_set_front_facing_winding; + uint32_t num_set_stencil_reference_value; + uint32_t num_set_depth_bias; + uint32_t num_set_render_pipeline_state; + uint32_t num_set_depth_stencil_state; +} sg_frame_stats_metal_pipeline; + +typedef struct sg_frame_stats_metal_bindings { + uint32_t num_set_vertex_buffer; + uint32_t num_set_vertex_texture; + uint32_t num_set_vertex_sampler_state; + uint32_t num_set_fragment_buffer; + uint32_t num_set_fragment_texture; + uint32_t num_set_fragment_sampler_state; + uint32_t num_set_compute_buffer; + uint32_t num_set_compute_texture; + uint32_t num_set_compute_sampler_state; +} sg_frame_stats_metal_bindings; + +typedef struct sg_frame_stats_metal_uniforms { + uint32_t num_set_vertex_buffer_offset; + uint32_t num_set_fragment_buffer_offset; + uint32_t num_set_compute_buffer_offset; +} sg_frame_stats_metal_uniforms; + +typedef struct sg_frame_stats_metal { + sg_frame_stats_metal_idpool idpool; + sg_frame_stats_metal_pipeline pipeline; + sg_frame_stats_metal_bindings bindings; + sg_frame_stats_metal_uniforms uniforms; +} sg_frame_stats_metal; + +typedef struct sg_frame_stats_wgpu_uniforms { + uint32_t num_set_bindgroup; + uint32_t size_write_buffer; +} sg_frame_stats_wgpu_uniforms; + +typedef struct sg_frame_stats_wgpu_bindings { + uint32_t num_set_vertex_buffer; + uint32_t num_skip_redundant_vertex_buffer; + uint32_t num_set_index_buffer; + uint32_t num_skip_redundant_index_buffer; + uint32_t num_create_bindgroup; + uint32_t num_discard_bindgroup; + uint32_t num_set_bindgroup; + uint32_t num_skip_redundant_bindgroup; + uint32_t num_bindgroup_cache_hits; + uint32_t num_bindgroup_cache_misses; + uint32_t num_bindgroup_cache_collisions; + uint32_t num_bindgroup_cache_invalidates; + uint32_t num_bindgroup_cache_hash_vs_key_mismatch; +} sg_frame_stats_wgpu_bindings; + +typedef struct sg_frame_stats_wgpu { + sg_frame_stats_wgpu_uniforms uniforms; + sg_frame_stats_wgpu_bindings bindings; +} sg_frame_stats_wgpu; + +typedef struct sg_frame_stats { + uint32_t frame_index; // current frame counter, starts at 0 + + uint32_t num_passes; + uint32_t num_apply_viewport; + uint32_t num_apply_scissor_rect; + uint32_t num_apply_pipeline; + uint32_t num_apply_bindings; + uint32_t num_apply_uniforms; + uint32_t num_draw; + uint32_t num_dispatch; + uint32_t num_update_buffer; + uint32_t num_append_buffer; + uint32_t num_update_image; + + uint32_t size_apply_uniforms; + uint32_t size_update_buffer; + uint32_t size_append_buffer; + uint32_t size_update_image; + + sg_frame_stats_gl gl; + sg_frame_stats_d3d11 d3d11; + sg_frame_stats_metal metal; + sg_frame_stats_wgpu wgpu; +} sg_frame_stats; + +/* + sg_log_item + + An enum with a unique item for each log message, warning, error + and validation layer message. Note that these messages are only + visible when a logger function is installed in the sg_setup() call. +*/ +#define _SG_LOG_ITEMS \ + _SG_LOGITEM_XMACRO(OK, "Ok") \ + _SG_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SG_LOGITEM_XMACRO(GL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (gl)") \ + _SG_LOGITEM_XMACRO(GL_3D_TEXTURES_NOT_SUPPORTED, "3d textures not supported (gl)") \ + _SG_LOGITEM_XMACRO(GL_ARRAY_TEXTURES_NOT_SUPPORTED, "array textures not supported (gl)") \ + _SG_LOGITEM_XMACRO(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE, "GLSL storage buffer bindslot is out of range (must be 0..7) (gl)") \ + _SG_LOGITEM_XMACRO(GL_SHADER_COMPILATION_FAILED, "shader compilation failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_SHADER_LINKING_FAILED, "shader linking failed (gl)") \ + _SG_LOGITEM_XMACRO(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, "vertex attribute not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \ + _SG_LOGITEM_XMACRO(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER, "uniform block name not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \ + _SG_LOGITEM_XMACRO(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, "image-sampler name not found in shader; NOTE: may be caused by GL driver's GLSL compiler removing unused globals") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNDEFINED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNDEFINED (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNSUPPORTED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNSUPPORTED (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE (gl)") \ + _SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNKNOWN, "framebuffer completeness check failed (unknown reason) (gl)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_FAILED, "CreateBuffer() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_SRV_FAILED, "CreateShaderResourceView() failed for storage buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_UAV_FAILED, "CreateUnorderedAccessView() failed for storage buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for depth-stencil texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_FAILED, "CreateTexture2D() failed for depth-stencil texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_TEXTURE_FAILED, "CreateTexture2D() failed for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_2D_SRV_FAILED, "CreateShaderResourceView() failed for 2d-, cube- or array-texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for 3D texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_TEXTURE_FAILED, "CreateTexture3D() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_3D_SRV_FAILED, "CreateShaderResourceView() failed for 3d texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_MSAA_TEXTURE_FAILED, "CreateTexture2D() failed for MSAA render target texture (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_SAMPLER_STATE_FAILED, "CreateSamplerState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE, "uniform block 'hlsl_register_b_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE, "storage buffer 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE, "storage buffer 'hlsl_register_u_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE, "image 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE, "sampler 'hlsl_register_s_n' is out of rang (must be 0..15)") \ + _SG_LOGITEM_XMACRO(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED, "loading d3dcompiler_47.dll failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_FAILED, "shader compilation failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_SHADER_COMPILATION_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_CONSTANT_BUFFER_FAILED, "CreateBuffer() failed for uniform constant buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_INPUT_LAYOUT_FAILED, "CreateInputLayout() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_RASTERIZER_STATE_FAILED, "CreateRasterizerState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED, "CreateDepthStencilState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_BLEND_STATE_FAILED, "CreateBlendState() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_RTV_FAILED, "CreateRenderTargetView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_CREATE_DSV_FAILED, "CreateDepthStencilView() failed (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED, "Map() failed when updating buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_APPEND_BUFFER_FAILED, "Map() failed when appending to buffer (d3d11)") \ + _SG_LOGITEM_XMACRO(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED, "Map() failed when updating image (d3d11)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_BUFFER_FAILED, "failed to create buffer object (metal)") \ + _SG_LOGITEM_XMACRO(METAL_TEXTURE_FORMAT_NOT_SUPPORTED, "pixel format not supported for texture (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_TEXTURE_FAILED, "failed to create texture object (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_SAMPLER_FAILED, "failed to create sampler object (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_FAILED, "shader compilation failed (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_CREATION_FAILED, "shader creation failed (metal)") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_COMPILATION_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_SHADER_ENTRY_NOT_FOUND, "shader entry function not found (metal)") \ + _SG_LOGITEM_XMACRO(METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE, "uniform block 'msl_buffer_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE, "storage buffer 'msl_buffer_n' is out of range (must be 8..15)") \ + _SG_LOGITEM_XMACRO(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE, "image 'msl_texture_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE, "sampler 'msl_sampler_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_CPS_FAILED, "failed to create compute pipeline state (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_CPS_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_FAILED, "failed to create render pipeline state (metal)") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_RPS_OUTPUT, "") \ + _SG_LOGITEM_XMACRO(METAL_CREATE_DSS_FAILED, "failed to create depth stencil state (metal)") \ + _SG_LOGITEM_XMACRO(WGPU_BINDGROUPS_POOL_EXHAUSTED, "bindgroups pool exhausted (increase sg_desc.bindgroups_cache_size) (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE, "sg_desc.wgpu_bindgroups_cache_size must be > 1 (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_BINDGROUPSCACHE_SIZE_POW2, "sg_desc.wgpu_bindgroups_cache_size must be a power of 2 (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_CREATEBINDGROUP_FAILED, "wgpuDeviceCreateBindGroup failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_BUFFER_FAILED, "wgpuDeviceCreateBuffer() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_TEXTURE_FAILED, "wgpuDeviceCreateTexture() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_TEXTURE_VIEW_FAILED, "wgpuTextureCreateView() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_SAMPLER_FAILED, "wgpuDeviceCreateSampler() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_SHADER_MODULE_FAILED, "wgpuDeviceCreateShaderModule() failed") \ + _SG_LOGITEM_XMACRO(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED, "wgpuDeviceCreateBindGroupLayout() for shader stage failed") \ + _SG_LOGITEM_XMACRO(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE, "uniform block 'wgsl_group0_binding_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage buffer 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "image 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "sampler 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_PIPELINE_LAYOUT_FAILED, "wgpuDeviceCreatePipelineLayout() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_RENDER_PIPELINE_FAILED, "wgpuDeviceCreateRenderPipeline() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_COMPUTE_PIPELINE_FAILED, "wgpuDeviceCreateComputePipeline() failed") \ + _SG_LOGITEM_XMACRO(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED, "wgpuTextureCreateView() failed in create attachments") \ + _SG_LOGITEM_XMACRO(IDENTICAL_COMMIT_LISTENER, "attempting to add identical commit listener") \ + _SG_LOGITEM_XMACRO(COMMIT_LISTENER_ARRAY_FULL, "commit listener array full") \ + _SG_LOGITEM_XMACRO(TRACE_HOOKS_NOT_ENABLED, "sg_install_trace_hooks() called, but SOKOL_TRACE_HOOKS is not defined") \ + _SG_LOGITEM_XMACRO(DEALLOC_BUFFER_INVALID_STATE, "sg_dealloc_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_IMAGE_INVALID_STATE, "sg_dealloc_image(): image must be in alloc state") \ + _SG_LOGITEM_XMACRO(DEALLOC_SAMPLER_INVALID_STATE, "sg_dealloc_sampler(): sampler must be in alloc state") \ + _SG_LOGITEM_XMACRO(DEALLOC_SHADER_INVALID_STATE, "sg_dealloc_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_PIPELINE_INVALID_STATE, "sg_dealloc_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(DEALLOC_ATTACHMENTS_INVALID_STATE, "sg_dealloc_attachments(): attachments must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_BUFFER_INVALID_STATE, "sg_init_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_IMAGE_INVALID_STATE, "sg_init_image(): image must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_SAMPLER_INVALID_STATE, "sg_init_sampler(): sampler must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_SHADER_INVALID_STATE, "sg_init_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_PIPELINE_INVALID_STATE, "sg_init_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(INIT_ATTACHMENTS_INVALID_STATE, "sg_init_attachments(): pass must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(UNINIT_BUFFER_INVALID_STATE, "sg_uninit_buffer(): buffer must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_IMAGE_INVALID_STATE, "sg_uninit_image(): image must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_SAMPLER_INVALID_STATE, "sg_uninit_sampler(): sampler must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_SHADER_INVALID_STATE, "sg_uninit_shader(): shader must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_PIPELINE_INVALID_STATE, "sg_uninit_pipeline(): pipeline must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(UNINIT_ATTACHMENTS_INVALID_STATE, "sg_uninit_attachments(): attachments must be in VALID or FAILED state") \ + _SG_LOGITEM_XMACRO(FAIL_BUFFER_INVALID_STATE, "sg_fail_buffer(): buffer must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_IMAGE_INVALID_STATE, "sg_fail_image(): image must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_SAMPLER_INVALID_STATE, "sg_fail_sampler(): sampler must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_SHADER_INVALID_STATE, "sg_fail_shader(): shader must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_PIPELINE_INVALID_STATE, "sg_fail_pipeline(): pipeline must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(FAIL_ATTACHMENTS_INVALID_STATE, "sg_fail_attachments(): attachments must be in ALLOC state") \ + _SG_LOGITEM_XMACRO(BUFFER_POOL_EXHAUSTED, "buffer pool exhausted") \ + _SG_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \ + _SG_LOGITEM_XMACRO(SAMPLER_POOL_EXHAUSTED, "sampler pool exhausted") \ + _SG_LOGITEM_XMACRO(SHADER_POOL_EXHAUSTED, "shader pool exhausted") \ + _SG_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted") \ + _SG_LOGITEM_XMACRO(PASS_POOL_EXHAUSTED, "pass pool exhausted") \ + _SG_LOGITEM_XMACRO(BEGINPASS_ATTACHMENT_INVALID, "sg_begin_pass: an attachment was provided that no longer exists") \ + _SG_LOGITEM_XMACRO(APPLY_BINDINGS_STORAGE_BUFFER_TRACKER_EXHAUSTED, "sg_apply_bindings: too many read/write storage buffers in pass (bump sg_desc.max_dispatch_calls_per_pass") \ + _SG_LOGITEM_XMACRO(DRAW_WITHOUT_BINDINGS, "attempting to draw without resource bindings") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_CANARY, "sg_buffer_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE, "sg_buffer_desc.size must be greater zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE, "sg_buffer_desc.size and .data.size must be equal") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE, "sg_buffer_desc.data.size expected to be zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_EXPECT_NO_DATA, "sg_buffer_desc.data.ptr must be null for dynamic/stream buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED, "storage buffers not supported by the backend 3D API (requires OpenGL >= 4.3)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4, "size of storage buffers must be a multiple of 4") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_NODATA, "sg_image_data: no data (.ptr and/or .size is zero)") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDATA_DATA_SIZE, "sg_image_data: data size doesn't match expected surface size") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_CANARY, "sg_image_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_WIDTH, "sg_image_desc.width must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_HEIGHT, "sg_image_desc.height must be > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_PIXELFORMAT, "invalid pixel format for render-target image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT, "invalid pixel format for non-render-target image") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT, "non-render-target images cannot be multisampled") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT, "MSAA not supported for this pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_NUM_MIPMAPS, "MSAA images must have num_mipmaps == 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_3D_IMAGE, "3D images cannot have a sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_MSAA_CUBE_IMAGE, "cube images cannot have sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE, "3D images cannot have a depth/stencil image format") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_IMMUTABLE, "render target images must be SG_USAGE_IMMUTABLE") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_RT_NO_DATA, "render target images cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_INJECTED_NO_DATA, "images with injected textures cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA, "dynamic/stream images cannot be initialized with data") \ + _SG_LOGITEM_XMACRO(VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE, "compressed images must be immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_CANARY, "sg_sampler_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING, "sg_sampler_desc.max_anisotropy > 1 requires min/mag/mipmap_filter to be SG_FILTER_LINEAR") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_CANARY, "sg_shader_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VERTEX_SOURCE, "vertex shader source code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_FRAGMENT_SOURCE, "fragment shader source code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPUTE_SOURCE, "compute shader source code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE, "vertex shader source or byte code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE, "fragment shader source or byte code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE, "compute shader source or byte code expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_INVALID_SHADER_COMBO, "cannot combine compute shaders with vertex or fragment shaders") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, "shader byte code length (in bytes) required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP, "sg_shader_desc.mtl_threads_per_threadgroup must be initialized for compute shaders (metal)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS, "uniform block members must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO, "bound uniform block size cannot be zero") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_OUT_OF_RANGE, "uniform block 'msl_buffer_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION, "uniform block 'msl_buffer_n' must be unique across uniform blocks and storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE, "uniform block 'hlsl_register_b_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION, "uniform block 'hlsl_register_b_n' must be unique across uniform blocks in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE, "uniform block 'wgsl_group0_binding_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION, "uniform block 'wgsl_group0_binding_n' must be unique across all uniform blocks") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS, "GL backend requires uniform block member declarations") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME, "uniform block member 'glsl_name' missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH, "size of uniform block members doesn't match uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT, "uniform array count must be >= 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE, "uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_OUT_OF_RANGE, "storage buffer 'msl_buffer_n' is out of range (must be 8..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION, "storage buffer 'msl_buffer_n' must be unique across uniform blocks and storage buffer in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE, "storage buffer 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION, "storage_buffer 'hlsl_register_t_n' must be unique across read-only storage buffers and images in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE, "storage buffer 'hlsl_register_u_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION, "storage_buffer 'hlsl_register_u_n' must be unique across read/write storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE, "storage buffer 'glsl_binding_n' is out of range (must be 0..7)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_COLLISION, "storage buffer 'glsl_binding_n' must be unique across shader stages") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "storage buffer 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION, "storage buffer 'wgsl_group1_binding_n' must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE, "image 'msl_texture_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_COLLISION, "image 'msl_texture_n' must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE, "image 'hlsl_register_t_n' is out of range (must be 0..23)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_COLLISION, "image 'hlsl_register_t_n' must be unique across images and storage buffers in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "image 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_COLLISION, "image 'wgsl_group1_binding_n' must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_OUT_OF_RANGE, "sampler 'msl_sampler_n' is out of range (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION, "sampler 'msl_sampler_n' must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE, "sampler 'hlsl_register_s_n' is out of rang (must be 0..15)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION, "sampler 'hlsl_register_s_n' must be unique in same shader stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE, "sampler 'wgsl_group1_binding_n' is out of range (must be 0..127)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION, "sampler 'wgsl_group1_binding_n' must be unique across all images, samplers and storage buffers") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE, "image-sampler-pair image slot index is out of range (sg_shader_desc.image_sampler_pairs[].image_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE, "image-sampler-pair sampler slot index is out of range (sg_shader_desc.image_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_STAGE_MISMATCH, "image-sampler-pair stage doesn't match referenced image stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH, "image-sampler-pair stage doesn't match referenced sampler stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_GLSL_NAME, "image-sampler-pair 'glsl_name' missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED, "image sample type UNFILTERABLE_FLOAT, UINT, SINT can only be used with NONFILTERING sampler") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED, "image sample type DEPTH can only be used with COMPARISON sampler") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS, "one or more images are not referenced by by image-sampler-pairs (sg_shader_desc.image_sampler_pairs[].image_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS, "one or more samplers are not referenced by image-sampler-pairs (sg_shader_desc.image_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG, "vertex attribute name/semantic string too long (max len 16)") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_CANARY, "sg_pipeline_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER, "sg_pipeline_desc.shader missing or invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED, "sg_pipeline_desc.shader must be a compute shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED, "sg_pipeline_desc.compute is false, but shader is a compute shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_NO_CONT_ATTRS, "sg_pipeline_desc.layout.attrs is not continuous") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "sg_pipeline_desc.layout.attrs[].format is incompatble with sg_shader_desc.attrs[].base_type") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4, "sg_pipeline_desc.layout.buffers[].stride must be multiple of 4") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_ATTR_SEMANTICS, "D3D11 missing vertex attribute semantics in shader") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS, "sg_pipeline_desc.shader: only readonly storage buffer bindings allowed in render pipelines") \ + _SG_LOGITEM_XMACRO(VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE, "SG_BLENDOP_MIN/MAX requires all blend factors to be SG_BLENDFACTOR_ONE") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_CANARY, "sg_attachments_desc not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_NO_ATTACHMENTS, "sg_attachments_desc no color or depth-stencil attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_NO_CONT_COLOR_ATTS, "color attachments must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_IMAGE, "pass attachment image is not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_MIPLEVEL, "pass attachment mip level is bigger than image has mipmaps") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_FACE, "pass attachment image is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_LAYER, "pass attachment image is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_SLICE, "pass attachment image is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_IMAGE_NO_RT, "pass attachment image must be have render_target=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_COLOR_INV_PIXELFORMAT, "pass color-attachment images must be renderable color pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_INV_PIXELFORMAT, "pass depth-attachment image must be depth or depth-stencil pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES, "all pass attachments must have the same size") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_IMAGE_SAMPLE_COUNTS, "all pass attachments must have the same sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_COLOR_IMAGE_MSAA, "pass resolve attachments must have a color attachment image with sample count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE, "pass resolve attachment image not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_SAMPLE_COUNT, "pass resolve attachment image sample count must be 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_MIPLEVEL, "pass resolve attachment mip level is bigger than image has mipmaps") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_FACE, "pass resolve attachment is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_LAYER, "pass resolve attachment is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_SLICE, "pass resolve attachment is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_NO_RT, "pass resolve attachment image must have render_target=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES, "pass resolve attachment size must match color attachment image size") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_FORMAT, "pass resolve attachment pixel format must match color attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE, "pass depth attachment image is not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_MIPLEVEL, "pass depth attachment mip level is bigger than image has mipmaps") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_FACE, "pass depth attachment image is cubemap, but face index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_LAYER, "pass depth attachment image is array texture, but layer index is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_SLICE, "pass depth attachment image is 3d texture, but slice value is too big") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_NO_RT, "pass depth attachment image must be have render_target=true") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES, "pass depth attachment image size must match color attachment image size") \ + _SG_LOGITEM_XMACRO(VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SAMPLE_COUNT, "pass depth attachment sample count must match color attachment sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_CANARY, "sg_begin_pass: pass struct not initialized") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_EXPECT_NO_ATTACHMENTS, "sg_begin_pass: compute passes cannot have attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_ATTACHMENTS_EXISTS, "sg_begin_pass: attachments object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_ATTACHMENTS_VALID, "sg_begin_pass: attachments object not in resource state VALID") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE, "sg_begin_pass: one or more color attachment images are not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE, "sg_begin_pass: one or more resolve attachment images are not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE, "sg_begin_pass: one or more depth-stencil attachment images are not valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH, "sg_begin_pass: expected pass.swapchain.width > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET, "sg_begin_pass: expected pass.swapchain.width == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT, "sg_begin_pass: expected pass.swapchain.height > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET, "sg_begin_pass: expected pass.swapchain.height == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT, "sg_begin_pass: expected pass.swapchain.sample_count > 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET, "sg_begin_pass: expected pass.swapchain.sample_count == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT, "sg_begin_pass: expected pass.swapchain.color_format to be valid") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET, "sg_begin_pass: expected pass.swapchain.color_format to be unset") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET, "sg_begin_pass: expected pass.swapchain.depth_format to be unset") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE, "sg_begin_pass: expected pass.swapchain.metal.current_drawable != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.current_drawable == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE, "sg_begin_pass: expected pass.swapchain.metal.depth_stencil_texture != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.depth_stencil_texture == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE, "sg_begin_pass: expected pass.swapchain.metal.msaa_color_texture != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET, "sg_begin_pass: expected pass.swapchain.metal.msaa_color_texture == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.render_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.render_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.resolve_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.resolve_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.d3d11.depth_stencil_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.d3d11.depth_stencil_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.render_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.render_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.resolve_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.resolve_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW, "sg_begin_pass: expected pass.swapchain.wgpu.depth_stencil_view != 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET, "sg_begin_pass: expected pass.swapchain.wgpu.depth_stencil_view == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET, "sg_begin_pass: expected pass.swapchain.gl.framebuffer == 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_AVP_RENDERPASS_EXPECTED, "sg_apply_viewport: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ASR_RENDERPASS_EXPECTED, "sg_apply_scissor_rect: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID_ID, "sg_apply_pipeline: invalid pipeline id provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_EXISTS, "sg_apply_pipeline: pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PIPELINE_VALID, "sg_apply_pipeline: pipeline object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_PASS_EXPECTED, "sg_apply_pipeline: must be called in a pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SHADER_EXISTS, "sg_apply_pipeline: shader object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SHADER_VALID, "sg_apply_pipeline: shader object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COMPUTEPASS_EXPECTED, "sg_apply_pipeline: trying to apply compute pipeline in render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_RENDERPASS_EXPECTED, "sg_apply_pipeline: trying to apply render pipeline in compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_CURPASS_ATTACHMENTS_EXISTS, "sg_apply_pipeline: current pass attachments no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_CURPASS_ATTACHMENTS_VALID, "sg_apply_pipeline: current pass attachments not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_ATT_COUNT, "sg_apply_pipeline: number of pipeline color attachments doesn't match number of pass color attachments") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_COLOR_FORMAT, "sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_DEPTH_FORMAT, "sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format") \ + _SG_LOGITEM_XMACRO(VALIDATE_APIP_SAMPLE_COUNT, "sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PASS_EXPECTED, "sg_apply_bindings: must be called in a pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EMPTY_BINDINGS, "sg_apply_bindings: the provided sg_bindings struct is empty") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE, "sg_apply_bindings: must be called after sg_apply_pipeline") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_EXISTS, "sg_apply_bindings: currently applied pipeline object no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_PIPELINE_VALID, "sg_apply_bindings: currently applied pipeline object not in valid state") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBS, "sg_apply_bindings: vertex buffer bindings not allowed in a compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_COMPUTE_EXPECTED_NO_IB, "sg_apply_bindings: index buffer binding not allowed in compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_VB, "sg_apply_bindings: vertex buffer binding is missing or buffer handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_EXISTS, "sg_apply_bindings: vertex buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_TYPE, "sg_apply_bindings: buffer in vertex buffer slot is not a SG_BUFFERTYPE_VERTEXBUFFER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VB_OVERFLOW, "sg_apply_bindings: buffer in vertex buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_NO_IB, "sg_apply_bindings: pipeline object defines indexed rendering, but no index buffer provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB, "sg_apply_bindings: pipeline object defines non-indexed rendering, but index buffer provided") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_EXISTS, "sg_apply_bindings: index buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_TYPE, "sg_apply_bindings: buffer in index buffer slot is not a SG_BUFFERTYPE_INDEXBUFFER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IB_OVERFLOW, "sg_apply_bindings: buffer in index buffer slot is overflown") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_IMAGE_BINDING, "sg_apply_bindings: image binding is missing or the image handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMG_EXISTS, "sg_apply_bindings: bound image no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_TYPE_MISMATCH, "sg_apply_bindings: type of bound image doesn't match shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_MULTISAMPLED_IMAGE, "sg_apply_bindings: expected image with sample_count > 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_IMAGE_MSAA, "sg_apply_bindings: cannot bind image with sample_count>1") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_FILTERABLE_IMAGE, "sg_apply_bindings: filterable image expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_DEPTH_IMAGE, "sg_apply_bindings: depth image expected") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_SAMPLER_BINDING, "sg_apply_bindings: sampler binding is missing or the sampler handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_COMPARISON but sampler has SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING but sampler doesn't have SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING, but sampler has SG_FILTER_LINEAR filters") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_SMP_EXISTS, "sg_apply_bindings: bound sampler no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_EXPECTED_STORAGEBUFFER_BINDING, "sg_apply_bindings: storage buffer binding is missing or the buffer handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_STORAGEBUFFER_EXISTS, "sg_apply_bindings: bound storage buffer no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_STORAGEBUFFER_BINDING_BUFFERTYPE, "sg_apply_bindings: buffer bound to storage buffer slot is not of type storage buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_STORAGEBUFFER_READWRITE_IMMUTABLE, "sg_apply_bindings: storage buffers bound as read/write must have usage immutable") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_PASS_EXPECTED, "sg_apply_uniforms: must be called in a pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \ + _SG_LOGITEM_XMACRO(VALIDATE_AU_SIZE, "sg_apply_uniforms: data size doesn't match declared uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_RENDERPASS_EXPECTED, "sg_draw: must be called in a render pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_BASEELEMENT, "sg_draw: base_element cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMELEMENTS, "sg_draw: num_elements cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_NUMINSTANCES, "sg_draw: num_instances cannot be < 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING, "sg_draw: call to sg_apply_bindings() and/or sg_apply_uniforms() missing after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED, "sg_dispatch: must be called in a compute pass") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSX, "sg_dispatch: num_groups_x must be >=0 and <65536") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSY, "sg_dispatch: num_groups_y must be >=0 and <65536") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_NUMGROUPSZ, "sg_dispatch: num_groups_z must be >=0 and <65536") \ + _SG_LOGITEM_XMACRO(VALIDATE_DISPATCH_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING, "sg_dispatch: call to sg_apply_bindings() and/or sg_apply_uniforms() missing after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_USAGE, "sg_update_buffer: cannot update immutable buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_SIZE, "sg_update_buffer: update size is bigger than buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_ONCE, "sg_update_buffer: only one update allowed per buffer and frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDATEBUF_APPEND, "sg_update_buffer: cannot call sg_update_buffer and sg_append_buffer in same frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_USAGE, "sg_append_buffer: cannot append to immutable buffer") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_SIZE, "sg_append_buffer: overall appended size is bigger than buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_APPENDBUF_UPDATE, "sg_append_buffer: cannot call sg_append_buffer and sg_update_buffer in same frame") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_USAGE, "sg_update_image: cannot update immutable image") \ + _SG_LOGITEM_XMACRO(VALIDATE_UPDIMG_ONCE, "sg_update_image: only one update allowed per image and frame") \ + _SG_LOGITEM_XMACRO(VALIDATION_FAILED, "validation layer checks failed") \ + +#define _SG_LOGITEM_XMACRO(item,msg) SG_LOGITEM_##item, +typedef enum sg_log_item { + _SG_LOG_ITEMS +} sg_log_item; +#undef _SG_LOGITEM_XMACRO + +/* + sg_desc + + The sg_desc struct contains configuration values for sokol_gfx, + it is used as parameter to the sg_setup() call. + + The default configuration is: + + .buffer_pool_size 128 + .image_pool_size 128 + .sampler_pool_size 64 + .shader_pool_size 32 + .pipeline_pool_size 64 + .attachments_pool_size 16 + .uniform_buffer_size 4 MB (4*1024*1024) + .max_dispatch_calls_per_pass 1024 + .max_commit_listeners 1024 + .disable_validation false + .mtl_force_managed_storage_mode false + .wgpu_disable_bindgroups_cache false + .wgpu_bindgroups_cache_size 1024 + + .allocator.alloc_fn 0 (in this case, malloc() will be called) + .allocator.free_fn 0 (in this case, free() will be called) + .allocator.user_data 0 + + .environment.defaults.color_format: default value depends on selected backend: + all GL backends: SG_PIXELFORMAT_RGBA8 + Metal and D3D11: SG_PIXELFORMAT_BGRA8 + WebGPU: *no default* (must be queried from WebGPU swapchain object) + .environment.defaults.depth_format: SG_PIXELFORMAT_DEPTH_STENCIL + .environment.defaults.sample_count: 1 + + Metal specific: + (NOTE: All Objective-C object references are transferred through + a bridged cast (__bridge const void*) to sokol_gfx, which will use an + unretained bridged cast (__bridge id) to retrieve the Objective-C + references back. Since the bridge cast is unretained, the caller + must hold a strong reference to the Objective-C object until sg_setup() + returns. + + .mtl_force_managed_storage_mode + when enabled, Metal buffers and texture resources are created in managed storage + mode, otherwise sokol-gfx will decide whether to create buffers and + textures in managed or shared storage mode (this is mainly a debugging option) + .mtl_use_command_buffer_with_retained_references + when true, the sokol-gfx Metal backend will use Metal command buffers which + bump the reference count of resource objects as long as they are inflight, + this is slower than the default command-buffer-with-unretained-references + method, this may be a workaround when confronted with lifetime validation + errors from the Metal validation layer until a proper fix has been implemented + .environment.metal.device + a pointer to the MTLDevice object + + D3D11 specific: + .environment.d3d11.device + a pointer to the ID3D11Device object, this must have been created + before sg_setup() is called + .environment.d3d11.device_context + a pointer to the ID3D11DeviceContext object + .d3d11_shader_debugging + set this to true to compile shaders which are provided as HLSL source + code with debug information and without optimization, this allows + shader debugging in tools like RenderDoc, to output source code + instead of byte code from sokol-shdc, omit the `--binary` cmdline + option + + WebGPU specific: + .wgpu_disable_bindgroups_cache + When this is true, the WebGPU backend will create and immediately + release a BindGroup object in the sg_apply_bindings() call, only + use this for debugging purposes. + .wgpu_bindgroups_cache_size + The size of the bindgroups cache for re-using BindGroup objects + between sg_apply_bindings() calls. The smaller the cache size, + the more likely are cache slot collisions which will cause + a BindGroups object to be destroyed and a new one created. + Use the information returned by sg_query_stats() to check + if this is a frequent occurrence, and increase the cache size as + needed (the default is 1024). + NOTE: wgpu_bindgroups_cache_size must be a power-of-2 number! + .environment.wgpu.device + a WGPUDevice handle + + When using sokol_gfx.h and sokol_app.h together, consider using the + helper function sglue_environment() in the sokol_glue.h header to + initialize the sg_desc.environment nested struct. sglue_environment() returns + a completely initialized sg_environment struct with information + provided by sokol_app.h. +*/ +typedef struct sg_environment_defaults { + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; +} sg_environment_defaults; + +typedef struct sg_metal_environment { + const void* device; +} sg_metal_environment; + +typedef struct sg_d3d11_environment { + const void* device; + const void* device_context; +} sg_d3d11_environment; + +typedef struct sg_wgpu_environment { + const void* device; +} sg_wgpu_environment; + +typedef struct sg_environment { + sg_environment_defaults defaults; + sg_metal_environment metal; + sg_d3d11_environment d3d11; + sg_wgpu_environment wgpu; +} sg_environment; + +/* + sg_commit_listener + + Used with function sg_add_commit_listener() to add a callback + which will be called in sg_commit(). This is useful for libraries + building on top of sokol-gfx to be notified about when a frame + ends (instead of having to guess, or add a manual 'new-frame' + function. +*/ +typedef struct sg_commit_listener { + void (*func)(void* user_data); + void* user_data; +} sg_commit_listener; + +/* + sg_allocator + + Used in sg_desc to provide custom memory-alloc and -free functions + to sokol_gfx.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sg_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sg_allocator; + +/* + sg_logger + + Used in sg_desc to provide a logging function. Please be aware + that without logging function, sokol-gfx will be completely + silent, e.g. it will not report errors, warnings and + validation layer messages. For maximum error verbosity, + compile in debug mode (e.g. NDEBUG *not* defined) and provide a + compatible logger function in the sg_setup() call + (for instance the standard logging function from sokol_log.h). +*/ +typedef struct sg_logger { + void (*func)( + const char* tag, // always "sg" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sg_logger; + +typedef struct sg_desc { + uint32_t _start_canary; + int buffer_pool_size; + int image_pool_size; + int sampler_pool_size; + int shader_pool_size; + int pipeline_pool_size; + int attachments_pool_size; + int uniform_buffer_size; + int max_dispatch_calls_per_pass; // max expected number of dispatch calls per pass (default: 1024) + int max_commit_listeners; + bool disable_validation; // disable validation layer even in debug mode, useful for tests + bool d3d11_shader_debugging; // if true, HLSL shaders are compiled with D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION + bool mtl_force_managed_storage_mode; // for debugging: use Metal managed storage mode for resources even with UMA + bool mtl_use_command_buffer_with_retained_references; // Metal: use a managed MTLCommandBuffer which ref-counts used resources + bool wgpu_disable_bindgroups_cache; // set to true to disable the WebGPU backend BindGroup cache + int wgpu_bindgroups_cache_size; // number of slots in the WebGPU bindgroup cache (must be 2^N) + sg_allocator allocator; + sg_logger logger; // optional log function override + sg_environment environment; + uint32_t _end_canary; +} sg_desc; + +// setup and misc functions +SOKOL_GFX_API_DECL void sg_setup(const sg_desc* desc); +SOKOL_GFX_API_DECL void sg_shutdown(void); +SOKOL_GFX_API_DECL bool sg_isvalid(void); +SOKOL_GFX_API_DECL void sg_reset_state_cache(void); +SOKOL_GFX_API_DECL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks); +SOKOL_GFX_API_DECL void sg_push_debug_group(const char* name); +SOKOL_GFX_API_DECL void sg_pop_debug_group(void); +SOKOL_GFX_API_DECL bool sg_add_commit_listener(sg_commit_listener listener); +SOKOL_GFX_API_DECL bool sg_remove_commit_listener(sg_commit_listener listener); + +// resource creation, destruction and updating +SOKOL_GFX_API_DECL sg_buffer sg_make_buffer(const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL sg_image sg_make_image(const sg_image_desc* desc); +SOKOL_GFX_API_DECL sg_sampler sg_make_sampler(const sg_sampler_desc* desc); +SOKOL_GFX_API_DECL sg_shader sg_make_shader(const sg_shader_desc* desc); +SOKOL_GFX_API_DECL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL sg_attachments sg_make_attachments(const sg_attachments_desc* desc); +SOKOL_GFX_API_DECL void sg_destroy_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_destroy_image(sg_image img); +SOKOL_GFX_API_DECL void sg_destroy_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_destroy_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_destroy_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_destroy_attachments(sg_attachments atts); +SOKOL_GFX_API_DECL void sg_update_buffer(sg_buffer buf, const sg_range* data); +SOKOL_GFX_API_DECL void sg_update_image(sg_image img, const sg_image_data* data); +SOKOL_GFX_API_DECL int sg_append_buffer(sg_buffer buf, const sg_range* data); +SOKOL_GFX_API_DECL bool sg_query_buffer_overflow(sg_buffer buf); +SOKOL_GFX_API_DECL bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size); + +// render and compute functions +SOKOL_GFX_API_DECL void sg_begin_pass(const sg_pass* pass); +SOKOL_GFX_API_DECL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left); +SOKOL_GFX_API_DECL void sg_apply_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_apply_bindings(const sg_bindings* bindings); +SOKOL_GFX_API_DECL void sg_apply_uniforms(int ub_slot, const sg_range* data); +SOKOL_GFX_API_DECL void sg_draw(int base_element, int num_elements, int num_instances); +SOKOL_GFX_API_DECL void sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z); +SOKOL_GFX_API_DECL void sg_end_pass(void); +SOKOL_GFX_API_DECL void sg_commit(void); + +// getting information +SOKOL_GFX_API_DECL sg_desc sg_query_desc(void); +SOKOL_GFX_API_DECL sg_backend sg_query_backend(void); +SOKOL_GFX_API_DECL sg_features sg_query_features(void); +SOKOL_GFX_API_DECL sg_limits sg_query_limits(void); +SOKOL_GFX_API_DECL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt); +SOKOL_GFX_API_DECL int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes); +SOKOL_GFX_API_DECL int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes); +// get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID) +SOKOL_GFX_API_DECL sg_resource_state sg_query_buffer_state(sg_buffer buf); +SOKOL_GFX_API_DECL sg_resource_state sg_query_image_state(sg_image img); +SOKOL_GFX_API_DECL sg_resource_state sg_query_sampler_state(sg_sampler smp); +SOKOL_GFX_API_DECL sg_resource_state sg_query_shader_state(sg_shader shd); +SOKOL_GFX_API_DECL sg_resource_state sg_query_pipeline_state(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_resource_state sg_query_attachments_state(sg_attachments atts); +// get runtime information about a resource +SOKOL_GFX_API_DECL sg_buffer_info sg_query_buffer_info(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_info sg_query_image_info(sg_image img); +SOKOL_GFX_API_DECL sg_sampler_info sg_query_sampler_info(sg_sampler smp); +SOKOL_GFX_API_DECL sg_shader_info sg_query_shader_info(sg_shader shd); +SOKOL_GFX_API_DECL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_attachments_info sg_query_attachments_info(sg_attachments atts); +// get desc structs matching a specific resource (NOTE that not all creation attributes may be provided) +SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_desc sg_query_image_desc(sg_image img); +SOKOL_GFX_API_DECL sg_sampler_desc sg_query_sampler_desc(sg_sampler smp); +SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_desc(sg_shader shd); +SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip); +SOKOL_GFX_API_DECL sg_attachments_desc sg_query_attachments_desc(sg_attachments atts); +// get resource creation desc struct with their default values replaced +SOKOL_GFX_API_DECL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc); +SOKOL_GFX_API_DECL sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc); +SOKOL_GFX_API_DECL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc); +SOKOL_GFX_API_DECL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL sg_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc* desc); +// assorted query functions +SOKOL_GFX_API_DECL size_t sg_query_buffer_size(sg_buffer buf); +SOKOL_GFX_API_DECL sg_buffer_type sg_query_buffer_type(sg_buffer buf); +SOKOL_GFX_API_DECL sg_usage sg_query_buffer_usage(sg_buffer buf); +SOKOL_GFX_API_DECL sg_image_type sg_query_image_type(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_width(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_height(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_num_slices(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_num_mipmaps(sg_image img); +SOKOL_GFX_API_DECL sg_pixel_format sg_query_image_pixelformat(sg_image img); +SOKOL_GFX_API_DECL sg_usage sg_query_image_usage(sg_image img); +SOKOL_GFX_API_DECL int sg_query_image_sample_count(sg_image img); + +// separate resource allocation and initialization (for async setup) +SOKOL_GFX_API_DECL sg_buffer sg_alloc_buffer(void); +SOKOL_GFX_API_DECL sg_image sg_alloc_image(void); +SOKOL_GFX_API_DECL sg_sampler sg_alloc_sampler(void); +SOKOL_GFX_API_DECL sg_shader sg_alloc_shader(void); +SOKOL_GFX_API_DECL sg_pipeline sg_alloc_pipeline(void); +SOKOL_GFX_API_DECL sg_attachments sg_alloc_attachments(void); +SOKOL_GFX_API_DECL void sg_dealloc_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_dealloc_image(sg_image img); +SOKOL_GFX_API_DECL void sg_dealloc_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_dealloc_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_dealloc_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_dealloc_attachments(sg_attachments attachments); +SOKOL_GFX_API_DECL void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc); +SOKOL_GFX_API_DECL void sg_init_image(sg_image img, const sg_image_desc* desc); +SOKOL_GFX_API_DECL void sg_init_sampler(sg_sampler smg, const sg_sampler_desc* desc); +SOKOL_GFX_API_DECL void sg_init_shader(sg_shader shd, const sg_shader_desc* desc); +SOKOL_GFX_API_DECL void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc); +SOKOL_GFX_API_DECL void sg_init_attachments(sg_attachments attachments, const sg_attachments_desc* desc); +SOKOL_GFX_API_DECL void sg_uninit_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_uninit_image(sg_image img); +SOKOL_GFX_API_DECL void sg_uninit_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_uninit_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_uninit_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_uninit_attachments(sg_attachments atts); +SOKOL_GFX_API_DECL void sg_fail_buffer(sg_buffer buf); +SOKOL_GFX_API_DECL void sg_fail_image(sg_image img); +SOKOL_GFX_API_DECL void sg_fail_sampler(sg_sampler smp); +SOKOL_GFX_API_DECL void sg_fail_shader(sg_shader shd); +SOKOL_GFX_API_DECL void sg_fail_pipeline(sg_pipeline pip); +SOKOL_GFX_API_DECL void sg_fail_attachments(sg_attachments atts); + +// frame stats +SOKOL_GFX_API_DECL void sg_enable_frame_stats(void); +SOKOL_GFX_API_DECL void sg_disable_frame_stats(void); +SOKOL_GFX_API_DECL bool sg_frame_stats_enabled(void); +SOKOL_GFX_API_DECL sg_frame_stats sg_query_frame_stats(void); + +/* Backend-specific structs and functions, these may come in handy for mixing + sokol-gfx rendering with 'native backend' rendering functions. + + This group of functions will be expanded as needed. +*/ + +typedef struct sg_d3d11_buffer_info { + const void* buf; // ID3D11Buffer* +} sg_d3d11_buffer_info; + +typedef struct sg_d3d11_image_info { + const void* tex2d; // ID3D11Texture2D* + const void* tex3d; // ID3D11Texture3D* + const void* res; // ID3D11Resource* (either tex2d or tex3d) + const void* srv; // ID3D11ShaderResourceView* +} sg_d3d11_image_info; + +typedef struct sg_d3d11_sampler_info { + const void* smp; // ID3D11SamplerState* +} sg_d3d11_sampler_info; + +typedef struct sg_d3d11_shader_info { + const void* cbufs[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; // ID3D11Buffer* (constant buffers by bind slot) + const void* vs; // ID3D11VertexShader* + const void* fs; // ID3D11PixelShader* +} sg_d3d11_shader_info; + +typedef struct sg_d3d11_pipeline_info { + const void* il; // ID3D11InputLayout* + const void* rs; // ID3D11RasterizerState* + const void* dss; // ID3D11DepthStencilState* + const void* bs; // ID3D11BlendState* +} sg_d3d11_pipeline_info; + +typedef struct sg_d3d11_attachments_info { + const void* color_rtv[SG_MAX_COLOR_ATTACHMENTS]; // ID3D11RenderTargetView + const void* resolve_rtv[SG_MAX_COLOR_ATTACHMENTS]; // ID3D11RenderTargetView + const void* dsv; // ID3D11DepthStencilView +} sg_d3d11_attachments_info; + +typedef struct sg_mtl_buffer_info { + const void* buf[SG_NUM_INFLIGHT_FRAMES]; // id + int active_slot; +} sg_mtl_buffer_info; + +typedef struct sg_mtl_image_info { + const void* tex[SG_NUM_INFLIGHT_FRAMES]; // id + int active_slot; +} sg_mtl_image_info; + +typedef struct sg_mtl_sampler_info { + const void* smp; // id +} sg_mtl_sampler_info; + +typedef struct sg_mtl_shader_info { + const void* vertex_lib; // id + const void* fragment_lib; // id + const void* vertex_func; // id + const void* fragment_func; // id +} sg_mtl_shader_info; + +typedef struct sg_mtl_pipeline_info { + const void* rps; // id + const void* dss; // id +} sg_mtl_pipeline_info; + +typedef struct sg_wgpu_buffer_info { + const void* buf; // WGPUBuffer +} sg_wgpu_buffer_info; + +typedef struct sg_wgpu_image_info { + const void* tex; // WGPUTexture + const void* view; // WGPUTextureView +} sg_wgpu_image_info; + +typedef struct sg_wgpu_sampler_info { + const void* smp; // WGPUSampler +} sg_wgpu_sampler_info; + +typedef struct sg_wgpu_shader_info { + const void* vs_mod; // WGPUShaderModule + const void* fs_mod; // WGPUShaderModule + const void* bgl; // WGPUBindGroupLayout; +} sg_wgpu_shader_info; + +typedef struct sg_wgpu_pipeline_info { + const void* render_pipeline; // WGPURenderPipeline + const void* compute_pipeline; // WGPUComputePipeline +} sg_wgpu_pipeline_info; + +typedef struct sg_wgpu_attachments_info { + const void* color_view[SG_MAX_COLOR_ATTACHMENTS]; // WGPUTextureView + const void* resolve_view[SG_MAX_COLOR_ATTACHMENTS]; // WGPUTextureView + const void* ds_view; // WGPUTextureView +} sg_wgpu_attachments_info; + +typedef struct sg_gl_buffer_info { + uint32_t buf[SG_NUM_INFLIGHT_FRAMES]; + int active_slot; +} sg_gl_buffer_info; + +typedef struct sg_gl_image_info { + uint32_t tex[SG_NUM_INFLIGHT_FRAMES]; + uint32_t tex_target; + uint32_t msaa_render_buffer; + int active_slot; +} sg_gl_image_info; + +typedef struct sg_gl_sampler_info { + uint32_t smp; +} sg_gl_sampler_info; + +typedef struct sg_gl_shader_info { + uint32_t prog; +} sg_gl_shader_info; + +typedef struct sg_gl_attachments_info { + uint32_t framebuffer; + uint32_t msaa_resolve_framebuffer[SG_MAX_COLOR_ATTACHMENTS]; +} sg_gl_attachments_info; + +// D3D11: return ID3D11Device +SOKOL_GFX_API_DECL const void* sg_d3d11_device(void); +// D3D11: return ID3D11DeviceContext +SOKOL_GFX_API_DECL const void* sg_d3d11_device_context(void); +// D3D11: get internal buffer resource objects +SOKOL_GFX_API_DECL sg_d3d11_buffer_info sg_d3d11_query_buffer_info(sg_buffer buf); +// D3D11: get internal image resource objects +SOKOL_GFX_API_DECL sg_d3d11_image_info sg_d3d11_query_image_info(sg_image img); +// D3D11: get internal sampler resource objects +SOKOL_GFX_API_DECL sg_d3d11_sampler_info sg_d3d11_query_sampler_info(sg_sampler smp); +// D3D11: get internal shader resource objects +SOKOL_GFX_API_DECL sg_d3d11_shader_info sg_d3d11_query_shader_info(sg_shader shd); +// D3D11: get internal pipeline resource objects +SOKOL_GFX_API_DECL sg_d3d11_pipeline_info sg_d3d11_query_pipeline_info(sg_pipeline pip); +// D3D11: get internal pass resource objects +SOKOL_GFX_API_DECL sg_d3d11_attachments_info sg_d3d11_query_attachments_info(sg_attachments atts); + +// Metal: return __bridge-casted MTLDevice +SOKOL_GFX_API_DECL const void* sg_mtl_device(void); +// Metal: return __bridge-casted MTLRenderCommandEncoder when inside render pass (otherwise zero) +SOKOL_GFX_API_DECL const void* sg_mtl_render_command_encoder(void); +// Metal: return __bridge-casted MTLComputeCommandEncoder when inside compute pass (otherwise zero) +SOKOL_GFX_API_DECL const void* sg_mtl_compute_command_encoder(void); +// Metal: get internal __bridge-casted buffer resource objects +SOKOL_GFX_API_DECL sg_mtl_buffer_info sg_mtl_query_buffer_info(sg_buffer buf); +// Metal: get internal __bridge-casted image resource objects +SOKOL_GFX_API_DECL sg_mtl_image_info sg_mtl_query_image_info(sg_image img); +// Metal: get internal __bridge-casted sampler resource objects +SOKOL_GFX_API_DECL sg_mtl_sampler_info sg_mtl_query_sampler_info(sg_sampler smp); +// Metal: get internal __bridge-casted shader resource objects +SOKOL_GFX_API_DECL sg_mtl_shader_info sg_mtl_query_shader_info(sg_shader shd); +// Metal: get internal __bridge-casted pipeline resource objects +SOKOL_GFX_API_DECL sg_mtl_pipeline_info sg_mtl_query_pipeline_info(sg_pipeline pip); + +// WebGPU: return WGPUDevice object +SOKOL_GFX_API_DECL const void* sg_wgpu_device(void); +// WebGPU: return WGPUQueue object +SOKOL_GFX_API_DECL const void* sg_wgpu_queue(void); +// WebGPU: return this frame's WGPUCommandEncoder +SOKOL_GFX_API_DECL const void* sg_wgpu_command_encoder(void); +// WebGPU: return WGPURenderPassEncoder of current pass (returns 0 when outside pass or in a compute pass) +SOKOL_GFX_API_DECL const void* sg_wgpu_render_pass_encoder(void); +// WebGPU: return WGPUComputePassEncoder of current pass (returns 0 when outside pass or in a render pass) +SOKOL_GFX_API_DECL const void* sg_wgpu_compute_pass_encoder(void); +// WebGPU: get internal buffer resource objects +SOKOL_GFX_API_DECL sg_wgpu_buffer_info sg_wgpu_query_buffer_info(sg_buffer buf); +// WebGPU: get internal image resource objects +SOKOL_GFX_API_DECL sg_wgpu_image_info sg_wgpu_query_image_info(sg_image img); +// WebGPU: get internal sampler resource objects +SOKOL_GFX_API_DECL sg_wgpu_sampler_info sg_wgpu_query_sampler_info(sg_sampler smp); +// WebGPU: get internal shader resource objects +SOKOL_GFX_API_DECL sg_wgpu_shader_info sg_wgpu_query_shader_info(sg_shader shd); +// WebGPU: get internal pipeline resource objects +SOKOL_GFX_API_DECL sg_wgpu_pipeline_info sg_wgpu_query_pipeline_info(sg_pipeline pip); +// WebGPU: get internal pass resource objects +SOKOL_GFX_API_DECL sg_wgpu_attachments_info sg_wgpu_query_attachments_info(sg_attachments atts); + +// GL: get internal buffer resource objects +SOKOL_GFX_API_DECL sg_gl_buffer_info sg_gl_query_buffer_info(sg_buffer buf); +// GL: get internal image resource objects +SOKOL_GFX_API_DECL sg_gl_image_info sg_gl_query_image_info(sg_image img); +// GL: get internal sampler resource objects +SOKOL_GFX_API_DECL sg_gl_sampler_info sg_gl_query_sampler_info(sg_sampler smp); +// GL: get internal shader resource objects +SOKOL_GFX_API_DECL sg_gl_shader_info sg_gl_query_shader_info(sg_shader shd); +// GL: get internal pass resource objects +SOKOL_GFX_API_DECL sg_gl_attachments_info sg_gl_query_attachments_info(sg_attachments atts); + +#ifdef __cplusplus +} // extern "C" + +// reference-based equivalents for c++ +inline void sg_setup(const sg_desc& desc) { return sg_setup(&desc); } + +inline sg_buffer sg_make_buffer(const sg_buffer_desc& desc) { return sg_make_buffer(&desc); } +inline sg_image sg_make_image(const sg_image_desc& desc) { return sg_make_image(&desc); } +inline sg_sampler sg_make_sampler(const sg_sampler_desc& desc) { return sg_make_sampler(&desc); } +inline sg_shader sg_make_shader(const sg_shader_desc& desc) { return sg_make_shader(&desc); } +inline sg_pipeline sg_make_pipeline(const sg_pipeline_desc& desc) { return sg_make_pipeline(&desc); } +inline sg_attachments sg_make_attachments(const sg_attachments_desc& desc) { return sg_make_attachments(&desc); } +inline void sg_update_image(sg_image img, const sg_image_data& data) { return sg_update_image(img, &data); } + +inline void sg_begin_pass(const sg_pass& pass) { return sg_begin_pass(&pass); } +inline void sg_apply_bindings(const sg_bindings& bindings) { return sg_apply_bindings(&bindings); } +inline void sg_apply_uniforms(int ub_slot, const sg_range& data) { return sg_apply_uniforms(ub_slot, &data); } + +inline sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc& desc) { return sg_query_buffer_defaults(&desc); } +inline sg_image_desc sg_query_image_defaults(const sg_image_desc& desc) { return sg_query_image_defaults(&desc); } +inline sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc& desc) { return sg_query_sampler_defaults(&desc); } +inline sg_shader_desc sg_query_shader_defaults(const sg_shader_desc& desc) { return sg_query_shader_defaults(&desc); } +inline sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc& desc) { return sg_query_pipeline_defaults(&desc); } +inline sg_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc& desc) { return sg_query_attachments_defaults(&desc); } + +inline void sg_init_buffer(sg_buffer buf, const sg_buffer_desc& desc) { return sg_init_buffer(buf, &desc); } +inline void sg_init_image(sg_image img, const sg_image_desc& desc) { return sg_init_image(img, &desc); } +inline void sg_init_sampler(sg_sampler smp, const sg_sampler_desc& desc) { return sg_init_sampler(smp, &desc); } +inline void sg_init_shader(sg_shader shd, const sg_shader_desc& desc) { return sg_init_shader(shd, &desc); } +inline void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc& desc) { return sg_init_pipeline(pip, &desc); } +inline void sg_init_attachments(sg_attachments atts, const sg_attachments_desc& desc) { return sg_init_attachments(atts, &desc); } + +inline void sg_update_buffer(sg_buffer buf_id, const sg_range& data) { return sg_update_buffer(buf_id, &data); } +inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_append_buffer(buf_id, &data); } +#endif +#endif // SOKOL_GFX_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_GFX_IMPL +#define SOKOL_GFX_IMPL_INCLUDED (1) + +#if !(defined(SOKOL_GLCORE)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_DUMMY_BACKEND)) +#error "Please select a backend with SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND" +#endif +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sg_desc.allocator to override memory allocation functions" +#endif + +#include // malloc, free, qsort +#include // memset +#include // FLT_MAX + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(SOKOL_TRACE_HOOKS) +#define _SG_TRACE_ARGS(fn, ...) if (_sg.hooks.fn) { _sg.hooks.fn(__VA_ARGS__, _sg.hooks.user_data); } +#define _SG_TRACE_NOARGS(fn) if (_sg.hooks.fn) { _sg.hooks.fn(_sg.hooks.user_data); } +#else +#define _SG_TRACE_ARGS(fn, ...) +#define _SG_TRACE_NOARGS(fn) +#endif + +// default clear values +#ifndef SG_DEFAULT_CLEAR_RED +#define SG_DEFAULT_CLEAR_RED (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_GREEN +#define SG_DEFAULT_CLEAR_GREEN (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_BLUE +#define SG_DEFAULT_CLEAR_BLUE (0.5f) +#endif +#ifndef SG_DEFAULT_CLEAR_ALPHA +#define SG_DEFAULT_CLEAR_ALPHA (1.0f) +#endif +#ifndef SG_DEFAULT_CLEAR_DEPTH +#define SG_DEFAULT_CLEAR_DEPTH (1.0f) +#endif +#ifndef SG_DEFAULT_CLEAR_STENCIL +#define SG_DEFAULT_CLEAR_STENCIL (0) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4115) // named type definition in parentheses +#pragma warning(disable:4505) // unreferenced local function has been removed +#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union (needed by d3d11.h) +#pragma warning(disable:4054) // 'type cast': from function pointer +#pragma warning(disable:4055) // 'type cast': from data pointer +#endif + +#if defined(SOKOL_D3D11) + #ifndef D3D11_NO_HELPERS + #define D3D11_NO_HELPERS + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #ifdef _MSC_VER + #pragma comment (lib, "kernel32") + #pragma comment (lib, "user32") + #pragma comment (lib, "dxgi") + #pragma comment (lib, "d3d11") + #endif +#elif defined(SOKOL_METAL) + // see https://clang.llvm.org/docs/LanguageExtensions.html#automatic-reference-counting + #if !defined(__cplusplus) + #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields) + #error "sokol_gfx.h requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)" + #endif + #endif + #include + #include + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #define _SG_TARGET_MACOS (1) + #else + #define _SG_TARGET_IOS (1) + #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR + #define _SG_TARGET_IOS_SIMULATOR (1) + #endif + #endif + #import + #import // needed for CAMetalDrawable +#elif defined(SOKOL_WGPU) + #include + #if defined(__EMSCRIPTEN__) + #include + #endif +#elif defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) + #define _SOKOL_ANY_GL (1) + + // include platform specific GL headers (or on Win32: use an embedded GL loader) + #if !defined(SOKOL_EXTERNAL_GL_LOADER) + #if defined(_WIN32) + #if defined(SOKOL_GLCORE) && !defined(SOKOL_EXTERNAL_GL_LOADER) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #define _SOKOL_USE_WIN32_GL_LOADER (1) + #pragma comment (lib, "kernel32") // GetProcAddress() + #define _SOKOL_GL_HAS_COMPUTE (1) + #endif + #elif defined(__APPLE__) + #include + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE + #include + #else + #include + #include + #endif + #elif defined(__EMSCRIPTEN__) + #if defined(SOKOL_GLES3) + #include + #endif + #elif defined(__ANDROID__) + #define _SOKOL_GL_HAS_COMPUTE (1) + #include + #elif defined(__linux__) || defined(__unix__) + #define _SOKOL_GL_HAS_COMPUTE (1) + #if defined(SOKOL_GLCORE) + #define GL_GLEXT_PROTOTYPES + #include + #else + #include + #include + #endif + #endif + #endif + + // optional GL loader definitions (only on Win32) + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + #define __gl_h_ 1 + #define __gl32_h_ 1 + #define __gl31_h_ 1 + #define __GL_H__ 1 + #define __glext_h_ 1 + #define __GLEXT_H_ 1 + #define __gltypes_h_ 1 + #define __glcorearb_h_ 1 + #define __gl_glcorearb_h_ 1 + #define GL_APIENTRY APIENTRY + + typedef unsigned int GLenum; + typedef unsigned int GLuint; + typedef int GLsizei; + typedef char GLchar; + typedef ptrdiff_t GLintptr; + typedef ptrdiff_t GLsizeiptr; + typedef double GLclampd; + typedef unsigned short GLushort; + typedef unsigned char GLubyte; + typedef unsigned char GLboolean; + typedef uint64_t GLuint64; + typedef double GLdouble; + typedef unsigned short GLhalf; + typedef float GLclampf; + typedef unsigned int GLbitfield; + typedef signed char GLbyte; + typedef short GLshort; + typedef void GLvoid; + typedef int64_t GLint64; + typedef float GLfloat; + typedef int GLint; + #define GL_INT_2_10_10_10_REV 0x8D9F + #define GL_R32F 0x822E + #define GL_PROGRAM_POINT_SIZE 0x8642 + #define GL_DEPTH_ATTACHMENT 0x8D00 + #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A + #define GL_COLOR_ATTACHMENT2 0x8CE2 + #define GL_COLOR_ATTACHMENT0 0x8CE0 + #define GL_R16F 0x822D + #define GL_COLOR_ATTACHMENT22 0x8CF6 + #define GL_DRAW_FRAMEBUFFER 0x8CA9 + #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 + #define GL_NUM_EXTENSIONS 0x821D + #define GL_INFO_LOG_LENGTH 0x8B84 + #define GL_VERTEX_SHADER 0x8B31 + #define GL_INCR 0x1E02 + #define GL_DYNAMIC_DRAW 0x88E8 + #define GL_STATIC_DRAW 0x88E4 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 + #define GL_TEXTURE_CUBE_MAP 0x8513 + #define GL_FUNC_SUBTRACT 0x800A + #define GL_FUNC_REVERSE_SUBTRACT 0x800B + #define GL_CONSTANT_COLOR 0x8001 + #define GL_DECR_WRAP 0x8508 + #define GL_R8 0x8229 + #define GL_LINEAR_MIPMAP_LINEAR 0x2703 + #define GL_ELEMENT_ARRAY_BUFFER 0x8893 + #define GL_SHORT 0x1402 + #define GL_DEPTH_TEST 0x0B71 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 + #define GL_LINK_STATUS 0x8B82 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 + #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E + #define GL_RGBA16F 0x881A + #define GL_CONSTANT_ALPHA 0x8003 + #define GL_READ_FRAMEBUFFER 0x8CA8 + #define GL_TEXTURE0 0x84C0 + #define GL_TEXTURE_MIN_LOD 0x813A + #define GL_CLAMP_TO_EDGE 0x812F + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_TEXTURE_WRAP_R 0x8072 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_NEAREST_MIPMAP_NEAREST 0x2700 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 + #define GL_SRC_ALPHA_SATURATE 0x0308 + #define GL_STREAM_DRAW 0x88E0 + #define GL_ONE 1 + #define GL_NEAREST_MIPMAP_LINEAR 0x2702 + #define GL_RGB10_A2 0x8059 + #define GL_RGBA8 0x8058 + #define GL_SRGB8_ALPHA8 0x8C43 + #define GL_COLOR_ATTACHMENT1 0x8CE1 + #define GL_RGBA4 0x8056 + #define GL_RGB8 0x8051 + #define GL_ARRAY_BUFFER 0x8892 + #define GL_STENCIL 0x1802 + #define GL_TEXTURE_2D 0x0DE1 + #define GL_DEPTH 0x1801 + #define GL_FRONT 0x0404 + #define GL_STENCIL_BUFFER_BIT 0x00000400 + #define GL_REPEAT 0x2901 + #define GL_RGBA 0x1908 + #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 + #define GL_DECR 0x1E03 + #define GL_FRAGMENT_SHADER 0x8B30 + #define GL_COMPUTE_SHADER 0x91B9 + #define GL_FLOAT 0x1406 + #define GL_TEXTURE_MAX_LOD 0x813B + #define GL_DEPTH_COMPONENT 0x1902 + #define GL_ONE_MINUS_DST_ALPHA 0x0305 + #define GL_COLOR 0x1800 + #define GL_TEXTURE_2D_ARRAY 0x8C1A + #define GL_TRIANGLES 0x0004 + #define GL_UNSIGNED_BYTE 0x1401 + #define GL_TEXTURE_MAG_FILTER 0x2800 + #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + #define GL_NONE 0 + #define GL_SRC_COLOR 0x0300 + #define GL_BYTE 0x1400 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A + #define GL_LINE_STRIP 0x0003 + #define GL_TEXTURE_3D 0x806F + #define GL_CW 0x0900 + #define GL_LINEAR 0x2601 + #define GL_RENDERBUFFER 0x8D41 + #define GL_GEQUAL 0x0206 + #define GL_COLOR_BUFFER_BIT 0x00004000 + #define GL_RGBA32F 0x8814 + #define GL_BLEND 0x0BE2 + #define GL_ONE_MINUS_SRC_ALPHA 0x0303 + #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 + #define GL_TEXTURE_WRAP_T 0x2803 + #define GL_TEXTURE_WRAP_S 0x2802 + #define GL_TEXTURE_MIN_FILTER 0x2801 + #define GL_LINEAR_MIPMAP_NEAREST 0x2701 + #define GL_EXTENSIONS 0x1F03 + #define GL_NO_ERROR 0 + #define GL_REPLACE 0x1E01 + #define GL_KEEP 0x1E00 + #define GL_CCW 0x0901 + #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 + #define GL_RGB 0x1907 + #define GL_TRIANGLE_STRIP 0x0005 + #define GL_FALSE 0 + #define GL_ZERO 0 + #define GL_CULL_FACE 0x0B44 + #define GL_INVERT 0x150A + #define GL_INT 0x1404 + #define GL_UNSIGNED_INT 0x1405 + #define GL_UNSIGNED_SHORT 0x1403 + #define GL_NEAREST 0x2600 + #define GL_SCISSOR_TEST 0x0C11 + #define GL_LEQUAL 0x0203 + #define GL_STENCIL_TEST 0x0B90 + #define GL_DITHER 0x0BD0 + #define GL_DEPTH_COMPONENT32F 0x8CAC + #define GL_EQUAL 0x0202 + #define GL_FRAMEBUFFER 0x8D40 + #define GL_RGB5 0x8050 + #define GL_LINES 0x0001 + #define GL_DEPTH_BUFFER_BIT 0x00000100 + #define GL_SRC_ALPHA 0x0302 + #define GL_INCR_WRAP 0x8507 + #define GL_LESS 0x0201 + #define GL_MULTISAMPLE 0x809D + #define GL_FRAMEBUFFER_BINDING 0x8CA6 + #define GL_BACK 0x0405 + #define GL_ALWAYS 0x0207 + #define GL_FUNC_ADD 0x8006 + #define GL_ONE_MINUS_DST_COLOR 0x0307 + #define GL_NOTEQUAL 0x0205 + #define GL_DST_COLOR 0x0306 + #define GL_COMPILE_STATUS 0x8B81 + #define GL_RED 0x1903 + #define GL_COLOR_ATTACHMENT3 0x8CE3 + #define GL_DST_ALPHA 0x0304 + #define GL_RGB5_A1 0x8057 + #define GL_GREATER 0x0204 + #define GL_POLYGON_OFFSET_FILL 0x8037 + #define GL_TRUE 1 + #define GL_NEVER 0x0200 + #define GL_POINTS 0x0000 + #define GL_ONE_MINUS_SRC_COLOR 0x0301 + #define GL_MIRRORED_REPEAT 0x8370 + #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D + #define GL_R11F_G11F_B10F 0x8C3A + #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B + #define GL_RGB9_E5 0x8C3D + #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E + #define GL_RGBA32UI 0x8D70 + #define GL_RGB32UI 0x8D71 + #define GL_RGBA16UI 0x8D76 + #define GL_RGB16UI 0x8D77 + #define GL_RGBA8UI 0x8D7C + #define GL_RGB8UI 0x8D7D + #define GL_RGBA32I 0x8D82 + #define GL_RGB32I 0x8D83 + #define GL_RGBA16I 0x8D88 + #define GL_RGB16I 0x8D89 + #define GL_RGBA8I 0x8D8E + #define GL_RGB8I 0x8D8F + #define GL_RED_INTEGER 0x8D94 + #define GL_RG 0x8227 + #define GL_RG_INTEGER 0x8228 + #define GL_R8 0x8229 + #define GL_R16 0x822A + #define GL_RG8 0x822B + #define GL_RG16 0x822C + #define GL_R16F 0x822D + #define GL_R32F 0x822E + #define GL_RG16F 0x822F + #define GL_RG32F 0x8230 + #define GL_R8I 0x8231 + #define GL_R8UI 0x8232 + #define GL_R16I 0x8233 + #define GL_R16UI 0x8234 + #define GL_R32I 0x8235 + #define GL_R32UI 0x8236 + #define GL_RG8I 0x8237 + #define GL_RG8UI 0x8238 + #define GL_RG16I 0x8239 + #define GL_RG16UI 0x823A + #define GL_RG32I 0x823B + #define GL_RG32UI 0x823C + #define GL_RGBA_INTEGER 0x8D99 + #define GL_R8_SNORM 0x8F94 + #define GL_RG8_SNORM 0x8F95 + #define GL_RGB8_SNORM 0x8F96 + #define GL_RGBA8_SNORM 0x8F97 + #define GL_R16_SNORM 0x8F98 + #define GL_RG16_SNORM 0x8F99 + #define GL_RGB16_SNORM 0x8F9A + #define GL_RGBA16_SNORM 0x8F9B + #define GL_RGBA16 0x805B + #define GL_MAX_TEXTURE_SIZE 0x0D33 + #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C + #define GL_MAX_3D_TEXTURE_SIZE 0x8073 + #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF + #define GL_MAX_VERTEX_ATTRIBS 0x8869 + #define GL_CLAMP_TO_BORDER 0x812D + #define GL_TEXTURE_BORDER_COLOR 0x1004 + #define GL_CURRENT_PROGRAM 0x8B8D + #define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A + #define GL_UNPACK_ALIGNMENT 0x0CF5 + #define GL_FRAMEBUFFER_SRGB 0x8DB9 + #define GL_TEXTURE_COMPARE_MODE 0x884C + #define GL_TEXTURE_COMPARE_FUNC 0x884D + #define GL_COMPARE_REF_TO_TEXTURE 0x884E + #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F + #define GL_TEXTURE_MAX_LEVEL 0x813D + #define GL_FRAMEBUFFER_UNDEFINED 0x8219 + #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 + #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 + #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD + #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 + #define GL_MAJOR_VERSION 0x821B + #define GL_MINOR_VERSION 0x821C + #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 + #define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 + #define GL_SHADER_STORAGE_BARRIER_BIT 0x2000 + #define GL_MIN 0x8007 + #define GL_MAX 0x8008 + #endif + + #ifndef GL_UNSIGNED_INT_2_10_10_10_REV + #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 + #endif + #ifndef GL_UNSIGNED_INT_24_8 + #define GL_UNSIGNED_INT_24_8 0x84FA + #endif + #ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE + #endif + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + #endif + #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + #endif + #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + #endif + #ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT + #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F + #endif + #ifndef GL_COMPRESSED_RED_RGTC1 + #define GL_COMPRESSED_RED_RGTC1 0x8DBB + #endif + #ifndef GL_COMPRESSED_SIGNED_RED_RGTC1 + #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC + #endif + #ifndef GL_COMPRESSED_RED_GREEN_RGTC2 + #define GL_COMPRESSED_RED_GREEN_RGTC2 0x8DBD + #endif + #ifndef GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 + #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2 0x8DBE + #endif + #ifndef GL_COMPRESSED_RGBA_BPTC_UNORM_ARB + #define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C + #endif + #ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB + #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D + #endif + #ifndef GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB + #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E + #endif + #ifndef GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB + #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F + #endif + #ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 + #endif + #ifndef GL_COMPRESSED_SRGB8_ETC2 + #define GL_COMPRESSED_SRGB8_ETC2 0x9275 + #endif + #ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + #endif + #ifndef GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC + #define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 + #endif + #ifndef GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 + #define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 + #endif + #ifndef GL_COMPRESSED_R11_EAC + #define GL_COMPRESSED_R11_EAC 0x9270 + #endif + #ifndef GL_COMPRESSED_SIGNED_R11_EAC + #define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 + #endif + #ifndef GL_COMPRESSED_RG11_EAC + #define GL_COMPRESSED_RG11_EAC 0x9272 + #endif + #ifndef GL_COMPRESSED_SIGNED_RG11_EAC + #define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 + #endif + #ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 + #endif + #ifndef GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR + #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 + #endif + #ifndef GL_DEPTH24_STENCIL8 + #define GL_DEPTH24_STENCIL8 0x88F0 + #endif + #ifndef GL_HALF_FLOAT + #define GL_HALF_FLOAT 0x140B + #endif + #ifndef GL_DEPTH_STENCIL + #define GL_DEPTH_STENCIL 0x84F9 + #endif + #ifndef GL_LUMINANCE + #define GL_LUMINANCE 0x1909 + #endif + #ifndef GL_COMPUTE_SHADER + #define GL_COMPUTE_SHADER 0x91B9 + #endif + #ifndef _SG_GL_CHECK_ERROR + #define _SG_GL_CHECK_ERROR() { SOKOL_ASSERT(glGetError() == GL_NO_ERROR); } + #endif +#endif + +#if defined(SOKOL_GLES3) + // on WebGL2, GL_FRAMEBUFFER_UNDEFINED technically doesn't exist (it is defined + // in the Emscripten headers, but may not exist in other WebGL2 shims) + // see: https://github.com/floooh/sokol/pull/933 + #ifndef GL_FRAMEBUFFER_UNDEFINED + #define GL_FRAMEBUFFER_UNDEFINED 0x8219 + #endif +#endif + +// make some GL constants generally available to simplify compilation, +// use of those constants will be filtered by runtime flags +#ifndef GL_SHADER_STORAGE_BUFFER +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs +// resource pool slots +typedef struct { + uint32_t id; + sg_resource_state state; +} _sg_slot_t; + +// resource pool housekeeping struct +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _sg_pool_t; + +_SOKOL_PRIVATE void _sg_pool_init(_sg_pool_t* pool, int num); +_SOKOL_PRIVATE void _sg_pool_discard(_sg_pool_t* pool); +_SOKOL_PRIVATE int _sg_pool_alloc_index(_sg_pool_t* pool); +_SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index); +_SOKOL_PRIVATE void _sg_slot_reset(_sg_slot_t* slot); +_SOKOL_PRIVATE uint32_t _sg_slot_alloc(_sg_pool_t* pool, _sg_slot_t* slot, int slot_index); +_SOKOL_PRIVATE int _sg_slot_index(uint32_t id); + +// resource func forward decls +struct _sg_pools_s; +struct _sg_buffer_s; +_SOKOL_PRIVATE struct _sg_buffer_s* _sg_lookup_buffer(const struct _sg_pools_s* p, uint32_t buf_id); + +// resource tracking (for keeping track of gpu-written storage resources +typedef struct { + uint32_t size; + uint32_t cur; + uint32_t* items; +} _sg_tracker_t; + +_SOKOL_PRIVATE void _sg_tracker_init(_sg_tracker_t* tracker, uint32_t num); +_SOKOL_PRIVATE void _sg_tracker_discard(_sg_tracker_t* tracker); +_SOKOL_PRIVATE void _sg_tracker_reset(_sg_tracker_t* tracker); +_SOKOL_PRIVATE bool _sg_tracker_add(_sg_tracker_t* tracker, uint32_t res_id); + +// constants +enum { + _SG_STRING_SIZE = 32, + _SG_SLOT_SHIFT = 16, + _SG_SLOT_MASK = (1<<_SG_SLOT_SHIFT)-1, + _SG_MAX_POOL_SIZE = (1<<_SG_SLOT_SHIFT), + _SG_DEFAULT_BUFFER_POOL_SIZE = 128, + _SG_DEFAULT_IMAGE_POOL_SIZE = 128, + _SG_DEFAULT_SAMPLER_POOL_SIZE = 64, + _SG_DEFAULT_SHADER_POOL_SIZE = 32, + _SG_DEFAULT_PIPELINE_POOL_SIZE = 64, + _SG_DEFAULT_ATTACHMENTS_POOL_SIZE = 16, + _SG_DEFAULT_UB_SIZE = 4 * 1024 * 1024, + _SG_DEFAULT_MAX_DISPATCH_CALLS_PER_PASS = 1024, + _SG_DEFAULT_MAX_COMMIT_LISTENERS = 1024, + _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE = 1024, +}; + +// fixed-size string +typedef struct { + char buf[_SG_STRING_SIZE]; +} _sg_str_t; + +// helper macros +#define _sg_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sg_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) +#define _sg_min(a,b) (((a)<(b))?(a):(b)) +#define _sg_max(a,b) (((a)>(b))?(a):(b)) +#define _sg_clamp(v,v0,v1) (((v)<(v0))?(v0):(((v)>(v1))?(v1):(v))) +#define _sg_fequal(val,cmp,delta) ((((val)-(cmp))> -(delta))&&(((val)-(cmp))<(delta))) +#define _sg_ispow2(val) ((val&(val-1))==0) +#define _sg_stats_add(key,val) {if(_sg.stats_enabled){ _sg.stats.key+=val;}} + +_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size); +_SOKOL_PRIVATE void _sg_free(void* ptr); +_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size); + +typedef struct { + int size; + int append_pos; + bool append_overflow; + uint32_t update_frame_index; + uint32_t append_frame_index; + int num_slots; + int active_slot; + sg_buffer_type type; + sg_usage usage; +} _sg_buffer_common_t; + +_SOKOL_PRIVATE void _sg_buffer_common_init(_sg_buffer_common_t* cmn, const sg_buffer_desc* desc) { + cmn->size = (int)desc->size; + cmn->append_pos = 0; + cmn->append_overflow = false; + cmn->update_frame_index = 0; + cmn->append_frame_index = 0; + cmn->num_slots = (desc->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->active_slot = 0; + cmn->type = desc->type; + cmn->usage = desc->usage; +} + +typedef struct { + uint32_t upd_frame_index; + int num_slots; + int active_slot; + sg_image_type type; + bool render_target; + int width; + int height; + int num_slices; + int num_mipmaps; + sg_usage usage; + sg_pixel_format pixel_format; + int sample_count; +} _sg_image_common_t; + +_SOKOL_PRIVATE void _sg_image_common_init(_sg_image_common_t* cmn, const sg_image_desc* desc) { + cmn->upd_frame_index = 0; + cmn->num_slots = (desc->usage == SG_USAGE_IMMUTABLE) ? 1 : SG_NUM_INFLIGHT_FRAMES; + cmn->active_slot = 0; + cmn->type = desc->type; + cmn->render_target = desc->render_target; + cmn->width = desc->width; + cmn->height = desc->height; + cmn->num_slices = desc->num_slices; + cmn->num_mipmaps = desc->num_mipmaps; + cmn->usage = desc->usage; + cmn->pixel_format = desc->pixel_format; + cmn->sample_count = desc->sample_count; +} + +typedef struct { + sg_filter min_filter; + sg_filter mag_filter; + sg_filter mipmap_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + sg_wrap wrap_w; + float min_lod; + float max_lod; + sg_border_color border_color; + sg_compare_func compare; + uint32_t max_anisotropy; +} _sg_sampler_common_t; + +_SOKOL_PRIVATE void _sg_sampler_common_init(_sg_sampler_common_t* cmn, const sg_sampler_desc* desc) { + cmn->min_filter = desc->min_filter; + cmn->mag_filter = desc->mag_filter; + cmn->mipmap_filter = desc->mipmap_filter; + cmn->wrap_u = desc->wrap_u; + cmn->wrap_v = desc->wrap_v; + cmn->wrap_w = desc->wrap_w; + cmn->min_lod = desc->min_lod; + cmn->max_lod = desc->max_lod; + cmn->border_color = desc->border_color; + cmn->compare = desc->compare; + cmn->max_anisotropy = desc->max_anisotropy; +} + +typedef struct { + sg_shader_attr_base_type base_type; +} _sg_shader_attr_t; + +typedef struct { + sg_shader_stage stage; + uint32_t size; +} _sg_shader_uniform_block_t; + +typedef struct { + sg_shader_stage stage; + bool readonly; +} _sg_shader_storage_buffer_t; + +typedef struct { + sg_shader_stage stage; + sg_image_type image_type; + sg_image_sample_type sample_type; + bool multisampled; +} _sg_shader_image_t; + +typedef struct { + sg_shader_stage stage; + sg_sampler_type sampler_type; +} _sg_shader_sampler_t; + +// combined image sampler mappings, only needed on GL +typedef struct { + sg_shader_stage stage; + uint8_t image_slot; + uint8_t sampler_slot; +} _sg_shader_image_sampler_t; + +typedef struct { + uint32_t required_bindings_and_uniforms; + bool is_compute; + _sg_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_shader_uniform_block_t uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + _sg_shader_storage_buffer_t storage_buffers[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + _sg_shader_image_t images[SG_MAX_IMAGE_BINDSLOTS]; + _sg_shader_sampler_t samplers[SG_MAX_SAMPLER_BINDSLOTS]; + _sg_shader_image_sampler_t image_samplers[SG_MAX_IMAGE_SAMPLER_PAIRS]; +} _sg_shader_common_t; + +_SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_shader_desc* desc) { + cmn->is_compute = desc->compute_func.source || desc->compute_func.bytecode.ptr; + for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + cmn->attrs[i].base_type = desc->attrs[i].base_type; + } + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + const sg_shader_uniform_block* src = &desc->uniform_blocks[i]; + _sg_shader_uniform_block_t* dst = &cmn->uniform_blocks[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= (1 << i); + dst->stage = src->stage; + dst->size = src->size; + } + } + const uint32_t required_bindings_flag = (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS); + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const sg_shader_storage_buffer* src = &desc->storage_buffers[i]; + _sg_shader_storage_buffer_t* dst = &cmn->storage_buffers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->readonly = src->readonly; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + const sg_shader_image* src = &desc->images[i]; + _sg_shader_image_t* dst = &cmn->images[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->image_type = src->image_type; + dst->sample_type = src->sample_type; + dst->multisampled = src->multisampled; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const sg_shader_sampler* src = &desc->samplers[i]; + _sg_shader_sampler_t* dst = &cmn->samplers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + dst->stage = src->stage; + dst->sampler_type = src->sampler_type; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_SAMPLER_PAIRS; i++) { + const sg_shader_image_sampler_pair* src = &desc->image_sampler_pairs[i]; + _sg_shader_image_sampler_t* dst = &cmn->image_samplers[i]; + if (src->stage != SG_SHADERSTAGE_NONE) { + dst->stage = src->stage; + SOKOL_ASSERT((src->image_slot >= 0) && (src->image_slot < SG_MAX_IMAGE_BINDSLOTS)); + SOKOL_ASSERT(desc->images[src->image_slot].stage == src->stage); + dst->image_slot = src->image_slot; + SOKOL_ASSERT((src->sampler_slot >= 0) && (src->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS)); + SOKOL_ASSERT(desc->samplers[src->sampler_slot].stage == src->stage); + dst->sampler_slot = src->sampler_slot; + } + } +} + +typedef struct { + bool vertex_buffer_layout_active[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + bool use_instanced_draw; + bool is_compute; + uint32_t required_bindings_and_uniforms; + sg_shader shader_id; + sg_vertex_layout_state layout; + sg_depth_state depth; + sg_stencil_state stencil; + int color_count; + sg_color_target_state colors[SG_MAX_COLOR_ATTACHMENTS]; + sg_primitive_type primitive_type; + sg_index_type index_type; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + int sample_count; + sg_color blend_color; + bool alpha_to_coverage_enabled; +} _sg_pipeline_common_t; + +_SOKOL_PRIVATE void _sg_pipeline_common_init(_sg_pipeline_common_t* cmn, const sg_pipeline_desc* desc) { + SOKOL_ASSERT((desc->color_count >= 0) && (desc->color_count <= SG_MAX_COLOR_ATTACHMENTS)); + + // FIXME: most of this isn't needed for compute pipelines + + const uint32_t required_bindings_flag = (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS); + for (int i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[i]; + if (a_state->format != SG_VERTEXFORMAT_INVALID) { + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + cmn->vertex_buffer_layout_active[a_state->buffer_index] = true; + cmn->required_bindings_and_uniforms |= required_bindings_flag; + } + } + cmn->is_compute = desc->compute; + cmn->use_instanced_draw = false; + cmn->shader_id = desc->shader; + cmn->layout = desc->layout; + cmn->depth = desc->depth; + cmn->stencil = desc->stencil; + cmn->color_count = desc->color_count; + for (int i = 0; i < desc->color_count; i++) { + cmn->colors[i] = desc->colors[i]; + } + cmn->primitive_type = desc->primitive_type; + cmn->index_type = desc->index_type; + if (cmn->index_type != SG_INDEXTYPE_NONE) { + cmn->required_bindings_and_uniforms |= required_bindings_flag; + } + cmn->cull_mode = desc->cull_mode; + cmn->face_winding = desc->face_winding; + cmn->sample_count = desc->sample_count; + cmn->blend_color = desc->blend_color; + cmn->alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; +} + +typedef struct { + sg_image image_id; + int mip_level; + int slice; +} _sg_attachment_common_t; + +typedef struct { + int width; + int height; + int num_colors; + _sg_attachment_common_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_attachment_common_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_attachment_common_t depth_stencil; +} _sg_attachments_common_t; + +_SOKOL_PRIVATE void _sg_attachment_common_init(_sg_attachment_common_t* cmn, const sg_attachment_desc* desc) { + cmn->image_id = desc->image; + cmn->mip_level = desc->mip_level; + cmn->slice = desc->slice; +} + +_SOKOL_PRIVATE void _sg_attachments_common_init(_sg_attachments_common_t* cmn, const sg_attachments_desc* desc, int width, int height) { + SOKOL_ASSERT((width > 0) && (height > 0)); + cmn->width = width; + cmn->height = height; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (desc->colors[i].image.id != SG_INVALID_ID) { + cmn->num_colors++; + _sg_attachment_common_init(&cmn->colors[i], &desc->colors[i]); + _sg_attachment_common_init(&cmn->resolves[i], &desc->resolves[i]); + } + } + if (desc->depth_stencil.image.id != SG_INVALID_ID) { + _sg_attachment_common_init(&cmn->depth_stencil, &desc->depth_stencil); + } +} + +#if defined(SOKOL_DUMMY_BACKEND) +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; +} _sg_dummy_buffer_t; +typedef _sg_dummy_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; +} _sg_dummy_image_t; +typedef _sg_dummy_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; +} _sg_dummy_sampler_t; +typedef _sg_dummy_sampler_t _sg_sampler_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; +} _sg_dummy_shader_t; +typedef _sg_dummy_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_shader_t* shader; + _sg_pipeline_common_t cmn; +} _sg_dummy_pipeline_t; +typedef _sg_dummy_pipeline_t _sg_pipeline_t; + +typedef struct { + _sg_image_t* image; +} _sg_dummy_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + _sg_dummy_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_dummy_attachment_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_dummy_attachment_t depth_stencil; + } dmy; +} _sg_dummy_attachments_t; +typedef _sg_dummy_attachments_t _sg_attachments_t; + +#elif defined(_SOKOL_ANY_GL) + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + GLuint buf[SG_NUM_INFLIGHT_FRAMES]; + bool injected; // if true, external buffers were injected with sg_buffer_desc.gl_buffers + bool gpu_dirty; // true if modified by GPU shader but memory barrier hasn't been issued yet + } gl; +} _sg_gl_buffer_t; +typedef _sg_gl_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + GLenum target; + GLuint msaa_render_buffer; + GLuint tex[SG_NUM_INFLIGHT_FRAMES]; + bool injected; // if true, external textures were injected with sg_image_desc.gl_textures + } gl; +} _sg_gl_image_t; +typedef _sg_gl_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + GLuint smp; + bool injected; // true if external sampler was injects in sg_sampler_desc.gl_sampler + } gl; +} _sg_gl_sampler_t; +typedef _sg_gl_sampler_t _sg_sampler_t; + +typedef struct { + GLint gl_loc; + sg_uniform_type type; + uint16_t count; + uint16_t offset; +} _sg_gl_uniform_t; + +typedef struct { + int num_uniforms; + _sg_gl_uniform_t uniforms[SG_MAX_UNIFORMBLOCK_MEMBERS]; +} _sg_gl_uniform_block_t; + +typedef struct { + _sg_str_t name; +} _sg_gl_shader_attr_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + GLuint prog; + _sg_gl_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_gl_uniform_block_t uniform_blocks[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t sbuf_binding[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + int8_t tex_slot[SG_MAX_IMAGE_SAMPLER_PAIRS]; // GL texture unit index + } gl; +} _sg_gl_shader_t; +typedef _sg_gl_shader_t _sg_shader_t; + +typedef struct { + int8_t vb_index; // -1 if attr is not enabled + int8_t divisor; // -1 if not initialized + uint8_t stride; + uint8_t size; + uint8_t normalized; + int offset; + GLenum type; + sg_shader_attr_base_type base_type; +} _sg_gl_attr_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + _sg_shader_t* shader; + struct { + _sg_gl_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + sg_depth_state depth; + sg_stencil_state stencil; + sg_primitive_type primitive_type; + sg_blend_state blend; + sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS]; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + int sample_count; + bool alpha_to_coverage_enabled; + } gl; +} _sg_gl_pipeline_t; +typedef _sg_gl_pipeline_t _sg_pipeline_t; + +typedef struct { + _sg_image_t* image; +} _sg_gl_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + GLuint fb; + _sg_gl_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_gl_attachment_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_gl_attachment_t depth_stencil; + GLuint msaa_resolve_framebuffer[SG_MAX_COLOR_ATTACHMENTS]; + } gl; +} _sg_gl_attachments_t; +typedef _sg_gl_attachments_t _sg_attachments_t; + +typedef struct { + _sg_gl_attr_t gl_attr; + GLuint gl_vbuf; +} _sg_gl_cache_attr_t; + +typedef struct { + GLenum target; + GLuint texture; + GLuint sampler; +} _sg_gl_cache_texture_sampler_bind_slot; + +#define _SG_GL_MAX_SBUF_BINDINGS (SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_GL_MAX_IMG_SMP_BINDINGS (SG_MAX_IMAGE_SAMPLER_PAIRS) +typedef struct { + sg_depth_state depth; + sg_stencil_state stencil; + sg_blend_state blend; + sg_color_mask color_write_mask[SG_MAX_COLOR_ATTACHMENTS]; + sg_cull_mode cull_mode; + sg_face_winding face_winding; + bool polygon_offset_enabled; + int sample_count; + sg_color blend_color; + bool alpha_to_coverage_enabled; + _sg_gl_cache_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + GLuint vertex_buffer; + GLuint index_buffer; + GLuint storage_buffer; // general bind point + GLuint storage_buffers[_SG_GL_MAX_SBUF_BINDINGS]; + GLuint stored_vertex_buffer; + GLuint stored_index_buffer; + GLuint stored_storage_buffer; + GLuint prog; + _sg_gl_cache_texture_sampler_bind_slot texture_samplers[_SG_GL_MAX_IMG_SMP_BINDINGS]; + _sg_gl_cache_texture_sampler_bind_slot stored_texture_sampler; + int cur_ib_offset; + GLenum cur_primitive_type; + GLenum cur_index_type; + GLenum cur_active_texture; + _sg_pipeline_t* cur_pipeline; + sg_pipeline cur_pipeline_id; +} _sg_gl_state_cache_t; + +typedef struct { + bool valid; + GLuint vao; + _sg_gl_state_cache_t cache; + bool ext_anisotropic; + GLint max_anisotropy; + sg_store_action color_store_actions[SG_MAX_COLOR_ATTACHMENTS]; + sg_store_action depth_store_action; + sg_store_action stencil_store_action; + #if _SOKOL_USE_WIN32_GL_LOADER + HINSTANCE opengl32_dll; + #endif +} _sg_gl_backend_t; + +#elif defined(SOKOL_D3D11) + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + ID3D11Buffer* buf; + ID3D11ShaderResourceView* srv; + ID3D11UnorderedAccessView* uav; + } d3d11; +} _sg_d3d11_buffer_t; +typedef _sg_d3d11_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + DXGI_FORMAT format; + ID3D11Texture2D* tex2d; + ID3D11Texture3D* tex3d; + ID3D11Resource* res; // either tex2d or tex3d + ID3D11ShaderResourceView* srv; + } d3d11; +} _sg_d3d11_image_t; +typedef _sg_d3d11_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + ID3D11SamplerState* smp; + } d3d11; +} _sg_d3d11_sampler_t; +typedef _sg_d3d11_sampler_t _sg_sampler_t; + +typedef struct { + _sg_str_t sem_name; + int sem_index; +} _sg_d3d11_shader_attr_t; + +#define _SG_D3D11_MAX_STAGE_UB_BINDINGS (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_SRV_BINDINGS (SG_MAX_IMAGE_BINDSLOTS + SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_UAV_BINDINGS (SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_D3D11_MAX_STAGE_SMP_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS) + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_d3d11_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + ID3D11VertexShader* vs; + ID3D11PixelShader* fs; + ID3D11ComputeShader* cs; + void* vs_blob; + size_t vs_blob_length; + uint8_t ub_register_b_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t img_register_t_n[SG_MAX_IMAGE_BINDSLOTS]; + uint8_t smp_register_s_n[SG_MAX_SAMPLER_BINDSLOTS]; + uint8_t sbuf_register_t_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + uint8_t sbuf_register_u_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + ID3D11Buffer* all_cbufs[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + ID3D11Buffer* vs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS]; + ID3D11Buffer* fs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS]; + ID3D11Buffer* cs_cbufs[_SG_D3D11_MAX_STAGE_UB_BINDINGS]; + } d3d11; +} _sg_d3d11_shader_t; +typedef _sg_d3d11_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + _sg_shader_t* shader; + struct { + UINT stencil_ref; + UINT vb_strides[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + D3D_PRIMITIVE_TOPOLOGY topology; + DXGI_FORMAT index_format; + ID3D11InputLayout* il; + ID3D11RasterizerState* rs; + ID3D11DepthStencilState* dss; + ID3D11BlendState* bs; + } d3d11; +} _sg_d3d11_pipeline_t; +typedef _sg_d3d11_pipeline_t _sg_pipeline_t; + +typedef struct { + _sg_image_t* image; + union { + ID3D11RenderTargetView* rtv; + ID3D11DepthStencilView* dsv; + } view; +} _sg_d3d11_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + _sg_d3d11_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_d3d11_attachment_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_d3d11_attachment_t depth_stencil; + } d3d11; +} _sg_d3d11_attachments_t; +typedef _sg_d3d11_attachments_t _sg_attachments_t; + +typedef struct { + bool valid; + ID3D11Device* dev; + ID3D11DeviceContext* ctx; + bool use_indexed_draw; + bool use_instanced_draw; + _sg_pipeline_t* cur_pipeline; + sg_pipeline cur_pipeline_id; + struct { + ID3D11RenderTargetView* render_view; + ID3D11RenderTargetView* resolve_view; + } cur_pass; + // on-demand loaded d3dcompiler_47.dll handles + HINSTANCE d3dcompiler_dll; + bool d3dcompiler_dll_load_failed; + pD3DCompile D3DCompile_func; + // global subresourcedata array for texture updates + D3D11_SUBRESOURCE_DATA subres_data[SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS]; +} _sg_d3d11_backend_t; + +#elif defined(SOKOL_METAL) + +#if defined(_SG_TARGET_MACOS) || defined(_SG_TARGET_IOS_SIMULATOR) +#define _SG_MTL_UB_ALIGN (256) +#else +#define _SG_MTL_UB_ALIGN (16) +#endif +#define _SG_MTL_INVALID_SLOT_INDEX (0) + +typedef struct { + uint32_t frame_index; // frame index at which it is safe to release this resource + int slot_index; +} _sg_mtl_release_item_t; + +typedef struct { + NSMutableArray* pool; + int num_slots; + int free_queue_top; + int* free_queue; + int release_queue_front; + int release_queue_back; + _sg_mtl_release_item_t* release_queue; +} _sg_mtl_idpool_t; + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + int buf[SG_NUM_INFLIGHT_FRAMES]; // index into _sg_mtl_pool + } mtl; +} _sg_mtl_buffer_t; +typedef _sg_mtl_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + int tex[SG_NUM_INFLIGHT_FRAMES]; + } mtl; +} _sg_mtl_image_t; +typedef _sg_mtl_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + int sampler_state; + } mtl; +} _sg_mtl_sampler_t; +typedef _sg_mtl_sampler_t _sg_sampler_t; + +typedef struct { + int mtl_lib; + int mtl_func; +} _sg_mtl_shader_func_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_mtl_shader_func_t vertex_func; + _sg_mtl_shader_func_t fragment_func; + _sg_mtl_shader_func_t compute_func; + MTLSize threads_per_threadgroup; + uint8_t ub_buffer_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t img_texture_n[SG_MAX_IMAGE_BINDSLOTS]; + uint8_t smp_sampler_n[SG_MAX_SAMPLER_BINDSLOTS]; + uint8_t sbuf_buffer_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + } mtl; +} _sg_mtl_shader_t; +typedef _sg_mtl_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + _sg_shader_t* shader; + struct { + MTLPrimitiveType prim_type; + int index_size; + MTLIndexType index_type; + MTLCullMode cull_mode; + MTLWinding winding; + uint32_t stencil_ref; + MTLSize threads_per_threadgroup; + int cps; // MTLComputePipelineState + int rps; // MTLRenderPipelineState + int dss; // MTLDepthStencilState + } mtl; +} _sg_mtl_pipeline_t; +typedef _sg_mtl_pipeline_t _sg_pipeline_t; + +typedef struct { + _sg_image_t* image; +} _sg_mtl_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + _sg_mtl_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_mtl_attachment_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_mtl_attachment_t depth_stencil; + } mtl; +} _sg_mtl_attachments_t; +typedef _sg_mtl_attachments_t _sg_attachments_t; + +// resource binding state cache +#define _SG_MTL_MAX_STAGE_UB_BINDINGS (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS (_SG_MTL_MAX_STAGE_UB_BINDINGS + SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_BUFFER_BINDINGS (_SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS + SG_MAX_VERTEXBUFFER_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_IMAGE_BINDINGS (SG_MAX_IMAGE_BINDSLOTS) +#define _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS (SG_MAX_SAMPLER_BINDSLOTS) +typedef struct { + const _sg_pipeline_t* cur_pipeline; + sg_pipeline cur_pipeline_id; + const _sg_buffer_t* cur_indexbuffer; + sg_buffer cur_indexbuffer_id; + int cur_indexbuffer_offset; + int cur_vs_buffer_offsets[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + sg_buffer cur_vs_buffer_ids[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + sg_buffer cur_fs_buffer_ids[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + sg_buffer cur_cs_buffer_ids[_SG_MTL_MAX_STAGE_BUFFER_BINDINGS]; + sg_image cur_vs_image_ids[_SG_MTL_MAX_STAGE_IMAGE_BINDINGS]; + sg_image cur_fs_image_ids[_SG_MTL_MAX_STAGE_IMAGE_BINDINGS]; + sg_image cur_cs_image_ids[_SG_MTL_MAX_STAGE_IMAGE_BINDINGS]; + sg_sampler cur_vs_sampler_ids[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS]; + sg_sampler cur_fs_sampler_ids[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS]; + sg_sampler cur_cs_sampler_ids[_SG_MTL_MAX_STAGE_SAMPLER_BINDINGS]; +} _sg_mtl_state_cache_t; + +typedef struct { + bool valid; + bool use_shared_storage_mode; + uint32_t cur_frame_rotate_index; + int ub_size; + int cur_ub_offset; + uint8_t* cur_ub_base_ptr; + _sg_mtl_state_cache_t state_cache; + _sg_mtl_idpool_t idpool; + dispatch_semaphore_t sem; + id device; + id cmd_queue; + id cmd_buffer; + id render_cmd_encoder; + id compute_cmd_encoder; + id cur_drawable; + id uniform_buffers[SG_NUM_INFLIGHT_FRAMES]; +} _sg_mtl_backend_t; + +#elif defined(SOKOL_WGPU) + +#define _SG_WGPU_ROWPITCH_ALIGN (256) +#define _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE (1<<16) // also see WGPULimits.maxUniformBufferBindingSize +#define _SG_WGPU_NUM_BINDGROUPS (2) // 0: uniforms, 1: images, samplers, storage buffers +#define _SG_WGPU_UB_BINDGROUP_INDEX (0) +#define _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX (1) +#define _SG_WGPU_MAX_UB_BINDGROUP_ENTRIES (SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_WGPU_MAX_UB_BINDGROUP_BIND_SLOTS (2 * SG_MAX_UNIFORMBLOCK_BINDSLOTS) +#define _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES (SG_MAX_IMAGE_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS + SG_MAX_STORAGEBUFFER_BINDSLOTS) +#define _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS (128) + +typedef struct _sg_buffer_s { + _sg_slot_t slot; + _sg_buffer_common_t cmn; + struct { + WGPUBuffer buf; + } wgpu; +} _sg_wgpu_buffer_t; +typedef _sg_wgpu_buffer_t _sg_buffer_t; + +typedef struct _sg_image_s { + _sg_slot_t slot; + _sg_image_common_t cmn; + struct { + WGPUTexture tex; + WGPUTextureView view; + } wgpu; +} _sg_wgpu_image_t; +typedef _sg_wgpu_image_t _sg_image_t; + +typedef struct _sg_sampler_s { + _sg_slot_t slot; + _sg_sampler_common_t cmn; + struct { + WGPUSampler smp; + } wgpu; +} _sg_wgpu_sampler_t; +typedef _sg_wgpu_sampler_t _sg_sampler_t; + +typedef struct { + WGPUShaderModule module; + _sg_str_t entry; +} _sg_wgpu_shader_func_t; + +typedef struct _sg_shader_s { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_wgpu_shader_func_t vertex_func; + _sg_wgpu_shader_func_t fragment_func; + _sg_wgpu_shader_func_t compute_func; + WGPUBindGroupLayout bgl_ub; + WGPUBindGroup bg_ub; + WGPUBindGroupLayout bgl_img_smp_sbuf; + // a mapping of sokol-gfx bind slots to setBindGroup dynamic-offset-array indices + uint8_t ub_num_dynoffsets; + uint8_t ub_dynoffsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + // indexed by sokol-gfx bind slot: + uint8_t ub_grp0_bnd_n[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + uint8_t img_grp1_bnd_n[SG_MAX_IMAGE_BINDSLOTS]; + uint8_t smp_grp1_bnd_n[SG_MAX_SAMPLER_BINDSLOTS]; + uint8_t sbuf_grp1_bnd_n[SG_MAX_STORAGEBUFFER_BINDSLOTS]; + } wgpu; +} _sg_wgpu_shader_t; +typedef _sg_wgpu_shader_t _sg_shader_t; + +typedef struct _sg_pipeline_s { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + _sg_shader_t* shader; + struct { + WGPURenderPipeline rpip; + WGPUComputePipeline cpip; + WGPUColor blend_color; + } wgpu; +} _sg_wgpu_pipeline_t; +typedef _sg_wgpu_pipeline_t _sg_pipeline_t; + +typedef struct { + _sg_image_t* image; + WGPUTextureView view; +} _sg_wgpu_attachment_t; + +typedef struct _sg_attachments_s { + _sg_slot_t slot; + _sg_attachments_common_t cmn; + struct { + _sg_wgpu_attachment_t colors[SG_MAX_COLOR_ATTACHMENTS]; + _sg_wgpu_attachment_t resolves[SG_MAX_COLOR_ATTACHMENTS]; + _sg_wgpu_attachment_t depth_stencil; + } wgpu; +} _sg_wgpu_attachments_t; +typedef _sg_wgpu_attachments_t _sg_attachments_t; + +// a pool of per-frame uniform buffers +typedef struct { + uint32_t num_bytes; + uint32_t offset; // current offset into buf + uint8_t* staging; // intermediate buffer for uniform data updates + WGPUBuffer buf; // the GPU-side uniform buffer + uint32_t bind_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; // NOTE: index is sokol-gfx ub slot index! +} _sg_wgpu_uniform_buffer_t; + +typedef struct { + uint32_t id; +} _sg_wgpu_bindgroup_handle_t; + +typedef enum { + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_NONE = 0, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_IMAGE = 0x00001111, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER = 0x00002222, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_STORAGEBUFFER = 0x00003333, + _SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE = 0x00004444, +} _sg_wgpu_bindgroups_cache_item_type_t; + +#define _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS (1 + _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES) +typedef struct { + uint64_t hash; + // the format of cache key items is BBBBTTTTIIIIIIII + // where + // - BBBB is 2x the WGPU binding + // - TTTT is the _sg_wgpu_bindgroups_cache_item_type_t + // - IIIIIIII is the resource id + // + // where the item type is a per-resource-type bit pattern + uint64_t items[_SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS]; +} _sg_wgpu_bindgroups_cache_key_t; + +typedef struct { + uint32_t num; // must be 2^n + uint32_t index_mask; // mask to turn hash into valid index + _sg_wgpu_bindgroup_handle_t* items; +} _sg_wgpu_bindgroups_cache_t; + +typedef struct { + _sg_slot_t slot; + WGPUBindGroup bindgroup; + _sg_wgpu_bindgroups_cache_key_t key; +} _sg_wgpu_bindgroup_t; + +typedef struct { + _sg_pool_t pool; + _sg_wgpu_bindgroup_t* bindgroups; +} _sg_wgpu_bindgroups_pool_t; + +typedef struct { + struct { + sg_buffer buffer; + uint64_t offset; + } vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + struct { + sg_buffer buffer; + uint64_t offset; + } ib; + _sg_wgpu_bindgroup_handle_t bg; +} _sg_wgpu_bindings_cache_t; + +// the WGPU backend state +typedef struct { + bool valid; + bool use_indexed_draw; + WGPUDevice dev; + WGPUSupportedLimits limits; + WGPUQueue queue; + WGPUCommandEncoder cmd_enc; + WGPURenderPassEncoder rpass_enc; + WGPUComputePassEncoder cpass_enc; + WGPUBindGroup empty_bind_group; + const _sg_pipeline_t* cur_pipeline; + sg_pipeline cur_pipeline_id; + _sg_wgpu_uniform_buffer_t uniform; + _sg_wgpu_bindings_cache_t bindings_cache; + _sg_wgpu_bindgroups_cache_t bindgroups_cache; + _sg_wgpu_bindgroups_pool_t bindgroups_pool; +} _sg_wgpu_backend_t; +#endif + +// POOL STRUCTS + +// this *MUST* remain 0 +#define _SG_INVALID_SLOT_INDEX (0) + +typedef struct _sg_pools_s { + _sg_pool_t buffer_pool; + _sg_pool_t image_pool; + _sg_pool_t sampler_pool; + _sg_pool_t shader_pool; + _sg_pool_t pipeline_pool; + _sg_pool_t attachments_pool; + _sg_buffer_t* buffers; + _sg_image_t* images; + _sg_sampler_t* samplers; + _sg_shader_t* shaders; + _sg_pipeline_t* pipelines; + _sg_attachments_t* attachments; +} _sg_pools_t; + +typedef struct { + int num; // number of allocated commit listener items + int upper; // the current upper index (no valid items past this point) + sg_commit_listener* items; +} _sg_commit_listeners_t; + +// resolved resource bindings struct +typedef struct { + _sg_pipeline_t* pip; + int vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + int ib_offset; + _sg_buffer_t* vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + _sg_buffer_t* ib; + _sg_image_t* imgs[SG_MAX_IMAGE_BINDSLOTS]; + _sg_sampler_t* smps[SG_MAX_SAMPLER_BINDSLOTS]; + _sg_buffer_t* sbufs[SG_MAX_STORAGEBUFFER_BINDSLOTS]; +} _sg_bindings_t; + +typedef struct { + bool sample; + bool filter; + bool render; + bool blend; + bool msaa; + bool depth; +} _sg_pixelformat_info_t; + +typedef struct { + bool valid; + sg_desc desc; // original desc with default values patched in + uint32_t frame_index; + struct { + bool valid; + bool in_pass; + bool is_compute; + sg_attachments atts_id; // SG_INVALID_ID in a swapchain pass + _sg_attachments_t* atts; // 0 in a swapchain pass + int width; + int height; + struct { + sg_pixel_format color_fmt; + sg_pixel_format depth_fmt; + int sample_count; + } swapchain; + } cur_pass; + sg_pipeline cur_pipeline; + bool next_draw_valid; + uint32_t required_bindings_and_uniforms; // used to check that bindings and uniforms are applied after applying pipeline + uint32_t applied_bindings_and_uniforms; // bits 0..7: uniform blocks, bit 8: bindings + #if defined(SOKOL_DEBUG) + sg_log_item validate_error; + #endif + struct { + _sg_tracker_t readwrite_sbufs; // tracks read/write storage buffers used in compute pass + } compute; + _sg_pools_t pools; + sg_backend backend; + sg_features features; + sg_limits limits; + _sg_pixelformat_info_t formats[_SG_PIXELFORMAT_NUM]; + bool stats_enabled; + sg_frame_stats stats; + sg_frame_stats prev_stats; + #if defined(_SOKOL_ANY_GL) + _sg_gl_backend_t gl; + #elif defined(SOKOL_METAL) + _sg_mtl_backend_t mtl; + #elif defined(SOKOL_D3D11) + _sg_d3d11_backend_t d3d11; + #elif defined(SOKOL_WGPU) + _sg_wgpu_backend_t wgpu; + #endif + #if defined(SOKOL_TRACE_HOOKS) + sg_trace_hooks hooks; + #endif + _sg_commit_listeners_t commit_listeners; +} _sg_state_t; +static _sg_state_t _sg; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SG_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sg_log_messages[] = { + _SG_LOG_ITEMS +}; +#undef _SG_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SG_PANIC(code) _sg_log(SG_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SG_ERROR(code) _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SG_WARN(code) _sg_log(SG_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SG_INFO(code) _sg_log(SG_LOGITEM_ ##code, 3, 0, __LINE__) +#define _SG_LOGMSG(code,msg) _sg_log(SG_LOGITEM_ ##code, 3, msg, __LINE__) +#define _SG_VALIDATE(cond,code) if (!(cond)){ _sg.validate_error = SG_LOGITEM_ ##code; _sg_log(SG_LOGITEM_ ##code, 1, 0, __LINE__); } + +static void _sg_log(sg_log_item log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_sg.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _sg_log_messages[log_item]; + } + #endif + _sg.desc.logger.func("sg", log_level, (uint32_t)log_item, msg, line_nr, filename, _sg.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory + +// a helper macro to clear a struct with potentially ARC'ed ObjC references +#if defined(SOKOL_METAL) + #if defined(__cplusplus) + #define _SG_CLEAR_ARC_STRUCT(type, item) { item = type(); } + #else + #define _SG_CLEAR_ARC_STRUCT(type, item) { item = (type) { 0 }; } + #endif +#else + #define _SG_CLEAR_ARC_STRUCT(type, item) { _sg_clear(&item, sizeof(item)); } +#endif + +_SOKOL_PRIVATE void _sg_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sg_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sg.desc.allocator.alloc_fn) { + ptr = _sg.desc.allocator.alloc_fn(size, _sg.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SG_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sg_malloc_clear(size_t size) { + void* ptr = _sg_malloc(size); + _sg_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sg_free(void* ptr) { + if (_sg.desc.allocator.free_fn) { + _sg.desc.allocator.free_fn(ptr, _sg.desc.allocator.user_data); + } else { + free(ptr); + } +} + +_SOKOL_PRIVATE bool _sg_strempty(const _sg_str_t* str) { + return 0 == str->buf[0]; +} + +_SOKOL_PRIVATE const char* _sg_strptr(const _sg_str_t* str) { + return &str->buf[0]; +} + +_SOKOL_PRIVATE void _sg_strcpy(_sg_str_t* dst, const char* src) { + SOKOL_ASSERT(dst); + if (src) { + #if defined(_MSC_VER) + strncpy_s(dst->buf, _SG_STRING_SIZE, src, (_SG_STRING_SIZE-1)); + #else + strncpy(dst->buf, src, _SG_STRING_SIZE); + #endif + dst->buf[_SG_STRING_SIZE-1] = 0; + } else { + _sg_clear(dst->buf, _SG_STRING_SIZE); + } +} + +// ██ ██ ███████ ██ ██████ ███████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ █████ ██ ██████ █████ ██████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ +// +// >>helpers +_SOKOL_PRIVATE uint32_t _sg_align_u32(uint32_t val, uint32_t align) { + SOKOL_ASSERT((align > 0) && ((align & (align - 1)) == 0)); + return (val + (align - 1)) & ~(align - 1); +} + +typedef struct { int x, y, w, h; } _sg_recti_t; + +_SOKOL_PRIVATE _sg_recti_t _sg_clipi(int x, int y, int w, int h, int clip_width, int clip_height) { + x = _sg_min(_sg_max(0, x), clip_width-1); + y = _sg_min(_sg_max(0, y), clip_height-1); + if ((x + w) > clip_width) { + w = clip_width - x; + } + if ((y + h) > clip_height) { + h = clip_height - y; + } + w = _sg_max(w, 1); + h = _sg_max(h, 1); + const _sg_recti_t res = { x, y, w, h }; + return res; +} + +_SOKOL_PRIVATE int _sg_vertexformat_bytesize(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return 4; + case SG_VERTEXFORMAT_FLOAT2: return 8; + case SG_VERTEXFORMAT_FLOAT3: return 12; + case SG_VERTEXFORMAT_FLOAT4: return 16; + case SG_VERTEXFORMAT_INT: return 4; + case SG_VERTEXFORMAT_INT2: return 8; + case SG_VERTEXFORMAT_INT3: return 12; + case SG_VERTEXFORMAT_INT4: return 16; + case SG_VERTEXFORMAT_UINT: return 4; + case SG_VERTEXFORMAT_UINT2: return 8; + case SG_VERTEXFORMAT_UINT3: return 12; + case SG_VERTEXFORMAT_UINT4: return 16; + case SG_VERTEXFORMAT_BYTE4: return 4; + case SG_VERTEXFORMAT_BYTE4N: return 4; + case SG_VERTEXFORMAT_UBYTE4: return 4; + case SG_VERTEXFORMAT_UBYTE4N: return 4; + case SG_VERTEXFORMAT_SHORT2: return 4; + case SG_VERTEXFORMAT_SHORT2N: return 4; + case SG_VERTEXFORMAT_USHORT2: return 4; + case SG_VERTEXFORMAT_USHORT2N: return 4; + case SG_VERTEXFORMAT_SHORT4: return 8; + case SG_VERTEXFORMAT_SHORT4N: return 8; + case SG_VERTEXFORMAT_USHORT4: return 8; + case SG_VERTEXFORMAT_USHORT4N: return 8; + case SG_VERTEXFORMAT_UINT10_N2: return 4; + case SG_VERTEXFORMAT_HALF2: return 4; + case SG_VERTEXFORMAT_HALF4: return 8; + case SG_VERTEXFORMAT_INVALID: return 0; + default: + SOKOL_UNREACHABLE; + return -1; + } +} + +_SOKOL_PRIVATE const char* _sg_vertexformat_to_string(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return "FLOAT"; + case SG_VERTEXFORMAT_FLOAT2: return "FLOAT2"; + case SG_VERTEXFORMAT_FLOAT3: return "FLOAT3"; + case SG_VERTEXFORMAT_FLOAT4: return "FLOAT4"; + case SG_VERTEXFORMAT_INT: return "INT"; + case SG_VERTEXFORMAT_INT2: return "INT2"; + case SG_VERTEXFORMAT_INT3: return "INT3"; + case SG_VERTEXFORMAT_INT4: return "INT4"; + case SG_VERTEXFORMAT_UINT: return "UINT"; + case SG_VERTEXFORMAT_UINT2: return "UINT2"; + case SG_VERTEXFORMAT_UINT3: return "UINT3"; + case SG_VERTEXFORMAT_UINT4: return "UINT4"; + case SG_VERTEXFORMAT_BYTE4: return "BYTE4"; + case SG_VERTEXFORMAT_BYTE4N: return "BYTE4N"; + case SG_VERTEXFORMAT_UBYTE4: return "UBYTE4"; + case SG_VERTEXFORMAT_UBYTE4N: return "UBYTE4N"; + case SG_VERTEXFORMAT_SHORT2: return "SHORT2"; + case SG_VERTEXFORMAT_SHORT2N: return "SHORT2N"; + case SG_VERTEXFORMAT_USHORT2: return "USHORT2"; + case SG_VERTEXFORMAT_USHORT2N: return "USHORT2N"; + case SG_VERTEXFORMAT_SHORT4: return "SHORT4"; + case SG_VERTEXFORMAT_SHORT4N: return "SHORT4N"; + case SG_VERTEXFORMAT_USHORT4: return "USHORT4"; + case SG_VERTEXFORMAT_USHORT4N: return "USHORT4N"; + case SG_VERTEXFORMAT_UINT10_N2: return "UINT10_N2"; + case SG_VERTEXFORMAT_HALF2: return "HALF2"; + case SG_VERTEXFORMAT_HALF4: return "HALF4"; + default: + SOKOL_UNREACHABLE; + return "INVALID"; + } +} + +_SOKOL_PRIVATE const char* _sg_shaderattrbasetype_to_string(sg_shader_attr_base_type b) { + switch (b) { + case SG_SHADERATTRBASETYPE_UNDEFINED: return "UNDEFINED"; + case SG_SHADERATTRBASETYPE_FLOAT: return "FLOAT"; + case SG_SHADERATTRBASETYPE_SINT: return "SINT"; + case SG_SHADERATTRBASETYPE_UINT: return "UINT"; + default: + SOKOL_UNREACHABLE; + return "INVALID"; + } +} + +_SOKOL_PRIVATE sg_shader_attr_base_type _sg_vertexformat_basetype(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: + case SG_VERTEXFORMAT_FLOAT2: + case SG_VERTEXFORMAT_FLOAT3: + case SG_VERTEXFORMAT_FLOAT4: + case SG_VERTEXFORMAT_HALF2: + case SG_VERTEXFORMAT_HALF4: + case SG_VERTEXFORMAT_BYTE4N: + case SG_VERTEXFORMAT_UBYTE4N: + case SG_VERTEXFORMAT_SHORT2N: + case SG_VERTEXFORMAT_USHORT2N: + case SG_VERTEXFORMAT_SHORT4N: + case SG_VERTEXFORMAT_USHORT4N: + case SG_VERTEXFORMAT_UINT10_N2: + return SG_SHADERATTRBASETYPE_FLOAT; + case SG_VERTEXFORMAT_INT: + case SG_VERTEXFORMAT_INT2: + case SG_VERTEXFORMAT_INT3: + case SG_VERTEXFORMAT_INT4: + case SG_VERTEXFORMAT_BYTE4: + case SG_VERTEXFORMAT_SHORT2: + case SG_VERTEXFORMAT_SHORT4: + return SG_SHADERATTRBASETYPE_SINT; + case SG_VERTEXFORMAT_UINT: + case SG_VERTEXFORMAT_UINT2: + case SG_VERTEXFORMAT_UINT3: + case SG_VERTEXFORMAT_UINT4: + case SG_VERTEXFORMAT_UBYTE4: + case SG_VERTEXFORMAT_USHORT2: + case SG_VERTEXFORMAT_USHORT4: + return SG_SHADERATTRBASETYPE_UINT; + default: + SOKOL_UNREACHABLE; + return SG_SHADERATTRBASETYPE_UNDEFINED; + } +} + +_SOKOL_PRIVATE uint32_t _sg_uniform_alignment(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { + if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { + return 1; + } else { + SOKOL_ASSERT(array_count > 0); + if (array_count == 1) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT3: + case SG_UNIFORMTYPE_INT4: + return 16; + case SG_UNIFORMTYPE_MAT4: + return 16; + default: + SOKOL_UNREACHABLE; + return 1; + } + } else { + return 16; + } + } +} + +_SOKOL_PRIVATE uint32_t _sg_uniform_size(sg_uniform_type type, int array_count, sg_uniform_layout ub_layout) { + SOKOL_ASSERT(array_count > 0); + if (array_count == 1) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_INT3: + return 12; + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT4: + return 16; + case SG_UNIFORMTYPE_MAT4: + return 64; + default: + SOKOL_UNREACHABLE; + return 0; + } + } else { + if (ub_layout == SG_UNIFORMLAYOUT_NATIVE) { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_INT: + return 4 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_INT2: + return 8 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_INT3: + return 12 * (uint32_t)array_count; + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT4: + return 16 * (uint32_t)array_count; + case SG_UNIFORMTYPE_MAT4: + return 64 * (uint32_t)array_count; + default: + SOKOL_UNREACHABLE; + return 0; + } + } else { + switch (type) { + case SG_UNIFORMTYPE_FLOAT: + case SG_UNIFORMTYPE_FLOAT2: + case SG_UNIFORMTYPE_FLOAT3: + case SG_UNIFORMTYPE_FLOAT4: + case SG_UNIFORMTYPE_INT: + case SG_UNIFORMTYPE_INT2: + case SG_UNIFORMTYPE_INT3: + case SG_UNIFORMTYPE_INT4: + return 16 * (uint32_t)array_count; + case SG_UNIFORMTYPE_MAT4: + return 64 * (uint32_t)array_count; + default: + SOKOL_UNREACHABLE; + return 0; + } + } + } +} + +_SOKOL_PRIVATE bool _sg_is_compressed_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC2_RGBA: + case SG_PIXELFORMAT_BC3_RGBA: + case SG_PIXELFORMAT_BC3_SRGBA: + case SG_PIXELFORMAT_BC4_R: + case SG_PIXELFORMAT_BC4_RSN: + case SG_PIXELFORMAT_BC5_RG: + case SG_PIXELFORMAT_BC5_RGSN: + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: + case SG_PIXELFORMAT_BC7_SRGBA: + case SG_PIXELFORMAT_ETC2_RGB8: + case SG_PIXELFORMAT_ETC2_SRGB8: + case SG_PIXELFORMAT_ETC2_RGB8A1: + case SG_PIXELFORMAT_ETC2_RGBA8: + case SG_PIXELFORMAT_ETC2_SRGB8A8: + case SG_PIXELFORMAT_EAC_R11: + case SG_PIXELFORMAT_EAC_R11SN: + case SG_PIXELFORMAT_EAC_RG11: + case SG_PIXELFORMAT_EAC_RG11SN: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + return true; + default: + return false; + } +} + +_SOKOL_PRIVATE bool _sg_is_valid_rendertarget_color_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].render && !_sg.formats[fmt_index].depth; +} + +_SOKOL_PRIVATE bool _sg_is_valid_rendertarget_depth_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index >= 0) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].render && _sg.formats[fmt_index].depth; +} + +_SOKOL_PRIVATE bool _sg_is_depth_or_depth_stencil_format(sg_pixel_format fmt) { + return (SG_PIXELFORMAT_DEPTH == fmt) || (SG_PIXELFORMAT_DEPTH_STENCIL == fmt); +} + +_SOKOL_PRIVATE bool _sg_is_depth_stencil_format(sg_pixel_format fmt) { + return (SG_PIXELFORMAT_DEPTH_STENCIL == fmt); +} + +_SOKOL_PRIVATE int _sg_pixelformat_bytesize(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: + case SG_PIXELFORMAT_R8SN: + case SG_PIXELFORMAT_R8UI: + case SG_PIXELFORMAT_R8SI: + return 1; + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_R16UI: + case SG_PIXELFORMAT_R16SI: + case SG_PIXELFORMAT_R16F: + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8SN: + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RG8SI: + return 2; + case SG_PIXELFORMAT_R32UI: + case SG_PIXELFORMAT_R32SI: + case SG_PIXELFORMAT_R32F: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RG16UI: + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_RGBA8SI: + case SG_PIXELFORMAT_BGRA8: + case SG_PIXELFORMAT_RGB10A2: + case SG_PIXELFORMAT_RG11B10F: + case SG_PIXELFORMAT_RGB9E5: + return 4; + case SG_PIXELFORMAT_RG32UI: + case SG_PIXELFORMAT_RG32SI: + case SG_PIXELFORMAT_RG32F: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_RGBA16UI: + case SG_PIXELFORMAT_RGBA16SI: + case SG_PIXELFORMAT_RGBA16F: + return 8; + case SG_PIXELFORMAT_RGBA32UI: + case SG_PIXELFORMAT_RGBA32SI: + case SG_PIXELFORMAT_RGBA32F: + return 16; + case SG_PIXELFORMAT_DEPTH: + case SG_PIXELFORMAT_DEPTH_STENCIL: + return 4; + default: + SOKOL_UNREACHABLE; + return 0; + } +} + +_SOKOL_PRIVATE int _sg_roundup(int val, int round_to) { + return (val+(round_to-1)) & ~(round_to-1); +} + +_SOKOL_PRIVATE uint32_t _sg_roundup_u32(uint32_t val, uint32_t round_to) { + return (val+(round_to-1)) & ~(round_to-1); +} + +_SOKOL_PRIVATE uint64_t _sg_roundup_u64(uint64_t val, uint64_t round_to) { + return (val+(round_to-1)) & ~(round_to-1); +} + +_SOKOL_PRIVATE bool _sg_multiple_u64(uint64_t val, uint64_t of) { + return (val & (of-1)) == 0; +} + +/* return row pitch for an image + + see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp +*/ +_SOKOL_PRIVATE int _sg_row_pitch(sg_pixel_format fmt, int width, int row_align) { + int pitch; + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC4_R: + case SG_PIXELFORMAT_BC4_RSN: + case SG_PIXELFORMAT_ETC2_RGB8: + case SG_PIXELFORMAT_ETC2_SRGB8: + case SG_PIXELFORMAT_ETC2_RGB8A1: + case SG_PIXELFORMAT_EAC_R11: + case SG_PIXELFORMAT_EAC_R11SN: + pitch = ((width + 3) / 4) * 8; + pitch = pitch < 8 ? 8 : pitch; + break; + case SG_PIXELFORMAT_BC2_RGBA: + case SG_PIXELFORMAT_BC3_RGBA: + case SG_PIXELFORMAT_BC3_SRGBA: + case SG_PIXELFORMAT_BC5_RG: + case SG_PIXELFORMAT_BC5_RGSN: + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: + case SG_PIXELFORMAT_BC7_SRGBA: + case SG_PIXELFORMAT_ETC2_RGBA8: + case SG_PIXELFORMAT_ETC2_SRGB8A8: + case SG_PIXELFORMAT_EAC_RG11: + case SG_PIXELFORMAT_EAC_RG11SN: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + pitch = ((width + 3) / 4) * 16; + pitch = pitch < 16 ? 16 : pitch; + break; + default: + pitch = width * _sg_pixelformat_bytesize(fmt); + break; + } + pitch = _sg_roundup(pitch, row_align); + return pitch; +} + +// compute the number of rows in a surface depending on pixel format +_SOKOL_PRIVATE int _sg_num_rows(sg_pixel_format fmt, int height) { + int num_rows; + switch (fmt) { + case SG_PIXELFORMAT_BC1_RGBA: + case SG_PIXELFORMAT_BC4_R: + case SG_PIXELFORMAT_BC4_RSN: + case SG_PIXELFORMAT_ETC2_RGB8: + case SG_PIXELFORMAT_ETC2_SRGB8: + case SG_PIXELFORMAT_ETC2_RGB8A1: + case SG_PIXELFORMAT_ETC2_RGBA8: + case SG_PIXELFORMAT_ETC2_SRGB8A8: + case SG_PIXELFORMAT_EAC_R11: + case SG_PIXELFORMAT_EAC_R11SN: + case SG_PIXELFORMAT_EAC_RG11: + case SG_PIXELFORMAT_EAC_RG11SN: + case SG_PIXELFORMAT_BC2_RGBA: + case SG_PIXELFORMAT_BC3_RGBA: + case SG_PIXELFORMAT_BC3_SRGBA: + case SG_PIXELFORMAT_BC5_RG: + case SG_PIXELFORMAT_BC5_RGSN: + case SG_PIXELFORMAT_BC6H_RGBF: + case SG_PIXELFORMAT_BC6H_RGBUF: + case SG_PIXELFORMAT_BC7_RGBA: + case SG_PIXELFORMAT_BC7_SRGBA: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + num_rows = ((height + 3) / 4); + break; + default: + num_rows = height; + break; + } + if (num_rows < 1) { + num_rows = 1; + } + return num_rows; +} + +// return size of a mipmap level +_SOKOL_PRIVATE int _sg_miplevel_dim(int base_dim, int mip_level) { + return _sg_max(base_dim >> mip_level, 1); +} + +/* return pitch of a 2D subimage / texture slice + see ComputePitch in https://github.com/microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexUtil.cpp +*/ +_SOKOL_PRIVATE int _sg_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align) { + int num_rows = _sg_num_rows(fmt, height); + return num_rows * _sg_row_pitch(fmt, width, row_align); +} + +// capability table pixel format helper functions +_SOKOL_PRIVATE void _sg_pixelformat_all(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->blend = true; + pfi->render = true; + pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_s(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sf(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_srmd(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->render = true; + pfi->msaa = true; + pfi->depth = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_srm(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->render = true; + pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfrm(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->render = true; + pfi->msaa = true; +} +_SOKOL_PRIVATE void _sg_pixelformat_sbrm(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->blend = true; + pfi->render = true; + pfi->msaa = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sbr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->blend = true; + pfi->render = true; +} + +_SOKOL_PRIVATE void _sg_pixelformat_sfbr(_sg_pixelformat_info_t* pfi) { + pfi->sample = true; + pfi->filter = true; + pfi->blend = true; + pfi->render = true; +} + +_SOKOL_PRIVATE sg_pass_action _sg_pass_action_defaults(const sg_pass_action* action) { + SOKOL_ASSERT(action); + sg_pass_action res = *action; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (res.colors[i].load_action == _SG_LOADACTION_DEFAULT) { + res.colors[i].load_action = SG_LOADACTION_CLEAR; + res.colors[i].clear_value.r = SG_DEFAULT_CLEAR_RED; + res.colors[i].clear_value.g = SG_DEFAULT_CLEAR_GREEN; + res.colors[i].clear_value.b = SG_DEFAULT_CLEAR_BLUE; + res.colors[i].clear_value.a = SG_DEFAULT_CLEAR_ALPHA; + } + if (res.colors[i].store_action == _SG_STOREACTION_DEFAULT) { + res.colors[i].store_action = SG_STOREACTION_STORE; + } + } + if (res.depth.load_action == _SG_LOADACTION_DEFAULT) { + res.depth.load_action = SG_LOADACTION_CLEAR; + res.depth.clear_value = SG_DEFAULT_CLEAR_DEPTH; + } + if (res.depth.store_action == _SG_STOREACTION_DEFAULT) { + res.depth.store_action = SG_STOREACTION_DONTCARE; + } + if (res.stencil.load_action == _SG_LOADACTION_DEFAULT) { + res.stencil.load_action = SG_LOADACTION_CLEAR; + res.stencil.clear_value = SG_DEFAULT_CLEAR_STENCIL; + } + if (res.stencil.store_action == _SG_STOREACTION_DEFAULT) { + res.stencil.store_action = SG_STOREACTION_DONTCARE; + } + return res; +} + +// ██████ ██ ██ ███ ███ ███ ███ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ██ ██ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>dummy backend +#if defined(SOKOL_DUMMY_BACKEND) + +_SOKOL_PRIVATE void _sg_dummy_setup_backend(const sg_desc* desc) { + SOKOL_ASSERT(desc); + _SOKOL_UNUSED(desc); + _sg.backend = SG_BACKEND_DUMMY; + for (int i = SG_PIXELFORMAT_R8; i < SG_PIXELFORMAT_BC1_RGBA; i++) { + _sg.formats[i].sample = true; + _sg.formats[i].filter = true; + _sg.formats[i].render = true; + _sg.formats[i].blend = true; + _sg.formats[i].msaa = true; + } + _sg.formats[SG_PIXELFORMAT_DEPTH].depth = true; + _sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL].depth = true; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_backend(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_dummy_reset_state_cache(void) { + // empty +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + _SOKOL_UNUSED(buf); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _SOKOL_UNUSED(buf); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + _SOKOL_UNUSED(img); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _SOKOL_UNUSED(img); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + _SOKOL_UNUSED(smp); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _SOKOL_UNUSED(smp); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + _SOKOL_UNUSED(shd); + _SOKOL_UNUSED(desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _SOKOL_UNUSED(shd); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && desc); + _SOKOL_UNUSED(desc); + pip->shader = shd; + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SOKOL_UNUSED(pip); +} + +_SOKOL_PRIVATE sg_resource_state _sg_dummy_create_attachments(_sg_attachments_t* atts, _sg_image_t** color_images, _sg_image_t** resolve_images, _sg_image_t* ds_img, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + SOKOL_ASSERT(color_images && resolve_images); + + for (int i = 0; i < atts->cmn.num_colors; i++) { + const sg_attachment_desc* color_desc = &desc->colors[i]; + _SOKOL_UNUSED(color_desc); + SOKOL_ASSERT(color_desc->image.id != SG_INVALID_ID); + SOKOL_ASSERT(0 == atts->dmy.colors[i].image); + SOKOL_ASSERT(color_images[i] && (color_images[i]->slot.id == color_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(color_images[i]->cmn.pixel_format)); + atts->dmy.colors[i].image = color_images[i]; + + const sg_attachment_desc* resolve_desc = &desc->resolves[i]; + if (resolve_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(0 == atts->dmy.resolves[i].image); + SOKOL_ASSERT(resolve_images[i] && (resolve_images[i]->slot.id == resolve_desc->image.id)); + SOKOL_ASSERT(color_images[i] && (color_images[i]->cmn.pixel_format == resolve_images[i]->cmn.pixel_format)); + atts->dmy.resolves[i].image = resolve_images[i]; + } + } + + SOKOL_ASSERT(0 == atts->dmy.depth_stencil.image); + const sg_attachment_desc* ds_desc = &desc->depth_stencil; + if (ds_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(ds_img && (ds_img->slot.id == ds_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(ds_img->cmn.pixel_format)); + atts->dmy.depth_stencil.image = ds_img; + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_dummy_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _SOKOL_UNUSED(atts); +} + +_SOKOL_PRIVATE _sg_image_t* _sg_dummy_attachments_color_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->dmy.colors[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_dummy_attachments_resolve_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->dmy.resolves[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_dummy_attachments_ds_image(const _sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + return atts->dmy.depth_stencil.image; +} + +_SOKOL_PRIVATE void _sg_dummy_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + _SOKOL_UNUSED(pass); +} + +_SOKOL_PRIVATE void _sg_dummy_end_pass(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_dummy_commit(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_dummy_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(w); + _SOKOL_UNUSED(h); + _SOKOL_UNUSED(origin_top_left); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(w); + _SOKOL_UNUSED(h); + _SOKOL_UNUSED(origin_top_left); +} + +_SOKOL_PRIVATE void _sg_dummy_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _SOKOL_UNUSED(pip); +} + +_SOKOL_PRIVATE bool _sg_dummy_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + _SOKOL_UNUSED(bnd); + return true; +} + +_SOKOL_PRIVATE void _sg_dummy_apply_uniforms(int ub_slot, const sg_range* data) { + _SOKOL_UNUSED(ub_slot); + _SOKOL_UNUSED(data); +} + +_SOKOL_PRIVATE void _sg_dummy_draw(int base_element, int num_elements, int num_instances) { + _SOKOL_UNUSED(base_element); + _SOKOL_UNUSED(num_elements); + _SOKOL_UNUSED(num_instances); +} + +_SOKOL_PRIVATE void _sg_dummy_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + _SOKOL_UNUSED(num_groups_x); + _SOKOL_UNUSED(num_groups_y); + _SOKOL_UNUSED(num_groups_z); +} + +_SOKOL_PRIVATE void _sg_dummy_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + _SOKOL_UNUSED(data); + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } +} + +_SOKOL_PRIVATE bool _sg_dummy_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + _SOKOL_UNUSED(data); + if (new_frame) { + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + } + return true; +} + +_SOKOL_PRIVATE void _sg_dummy_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + _SOKOL_UNUSED(data); + if (++img->cmn.active_slot >= img->cmn.num_slots) { + img->cmn.active_slot = 0; + } +} + +// ██████ ██████ ███████ ███ ██ ██████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██████ █████ ██ ██ ██ ██ ███ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ███████ ██ ████ ██████ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>opengl backend +#elif defined(_SOKOL_ANY_GL) + +// optional GL loader for win32 +#if defined(_SOKOL_USE_WIN32_GL_LOADER) + +#ifndef SG_GL_FUNCS_EXT +#define SG_GL_FUNCS_EXT +#endif + +// X Macro list of GL function names and signatures +#define _SG_GL_FUNCS \ + SG_GL_FUNCS_EXT \ + _SG_XMACRO(glBindVertexArray, void, (GLuint array)) \ + _SG_XMACRO(glFramebufferTextureLayer, void, (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)) \ + _SG_XMACRO(glGenFramebuffers, void, (GLsizei n, GLuint * framebuffers)) \ + _SG_XMACRO(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) \ + _SG_XMACRO(glBindRenderbuffer, void, (GLenum target, GLuint renderbuffer)) \ + _SG_XMACRO(glGetStringi, const GLubyte *, (GLenum name, GLuint index)) \ + _SG_XMACRO(glClearBufferfi, void, (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)) \ + _SG_XMACRO(glClearBufferfv, void, (GLenum buffer, GLint drawbuffer, const GLfloat * value)) \ + _SG_XMACRO(glClearBufferuiv, void, (GLenum buffer, GLint drawbuffer, const GLuint * value)) \ + _SG_XMACRO(glClearBufferiv, void, (GLenum buffer, GLint drawbuffer, const GLint * value)) \ + _SG_XMACRO(glDeleteRenderbuffers, void, (GLsizei n, const GLuint * renderbuffers)) \ + _SG_XMACRO(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform2fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform3fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform4fv, void, (GLint location, GLsizei count, const GLfloat * value)) \ + _SG_XMACRO(glUniform1iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform2iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform3iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniform4iv, void, (GLint location, GLsizei count, const GLint * value)) \ + _SG_XMACRO(glUniformMatrix4fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value)) \ + _SG_XMACRO(glUseProgram, void, (GLuint program)) \ + _SG_XMACRO(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length)) \ + _SG_XMACRO(glLinkProgram, void, (GLuint program)) \ + _SG_XMACRO(glGetUniformLocation, GLint, (GLuint program, const GLchar * name)) \ + _SG_XMACRO(glGetShaderiv, void, (GLuint shader, GLenum pname, GLint * params)) \ + _SG_XMACRO(glGetProgramInfoLog, void, (GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SG_XMACRO(glGetAttribLocation, GLint, (GLuint program, const GLchar * name)) \ + _SG_XMACRO(glDisableVertexAttribArray, void, (GLuint index)) \ + _SG_XMACRO(glDeleteShader, void, (GLuint shader)) \ + _SG_XMACRO(glDeleteProgram, void, (GLuint program)) \ + _SG_XMACRO(glCompileShader, void, (GLuint shader)) \ + _SG_XMACRO(glStencilFuncSeparate, void, (GLenum face, GLenum func, GLint ref, GLuint mask)) \ + _SG_XMACRO(glStencilOpSeparate, void, (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) \ + _SG_XMACRO(glRenderbufferStorageMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glDrawBuffers, void, (GLsizei n, const GLenum * bufs)) \ + _SG_XMACRO(glVertexAttribDivisor, void, (GLuint index, GLuint divisor)) \ + _SG_XMACRO(glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void * data)) \ + _SG_XMACRO(glGenBuffers, void, (GLsizei n, GLuint * buffers)) \ + _SG_XMACRO(glCheckFramebufferStatus, GLenum, (GLenum target)) \ + _SG_XMACRO(glFramebufferRenderbuffer, void, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) \ + _SG_XMACRO(glCompressedTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data)) \ + _SG_XMACRO(glCompressedTexImage3D, void, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data)) \ + _SG_XMACRO(glActiveTexture, void, (GLenum texture)) \ + _SG_XMACRO(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glRenderbufferStorage, void, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glGenTextures, void, (GLsizei n, GLuint * textures)) \ + _SG_XMACRO(glPolygonOffset, void, (GLfloat factor, GLfloat units)) \ + _SG_XMACRO(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const void * indices)) \ + _SG_XMACRO(glDeleteFramebuffers, void, (GLsizei n, const GLuint * framebuffers)) \ + _SG_XMACRO(glBlendEquationSeparate, void, (GLenum modeRGB, GLenum modeAlpha)) \ + _SG_XMACRO(glDeleteTextures, void, (GLsizei n, const GLuint * textures)) \ + _SG_XMACRO(glGetProgramiv, void, (GLuint program, GLenum pname, GLint * params)) \ + _SG_XMACRO(glBindTexture, void, (GLenum target, GLuint texture)) \ + _SG_XMACRO(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glCreateShader, GLuint, (GLenum type)) \ + _SG_XMACRO(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) \ + _SG_XMACRO(glCreateProgram, GLuint, (void)) \ + _SG_XMACRO(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glDeleteBuffers, void, (GLsizei n, const GLuint * buffers)) \ + _SG_XMACRO(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) \ + _SG_XMACRO(glDrawElementsInstanced, void, (GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount)) \ + _SG_XMACRO(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)) \ + _SG_XMACRO(glVertexAttribIPointer, void, (GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer)) \ + _SG_XMACRO(glUniform1i, void, (GLint location, GLint v0)) \ + _SG_XMACRO(glDisable, void, (GLenum cap)) \ + _SG_XMACRO(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SG_XMACRO(glColorMaski, void, (GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) \ + _SG_XMACRO(glBindBuffer, void, (GLenum target, GLuint buffer)) \ + _SG_XMACRO(glDeleteVertexArrays, void, (GLsizei n, const GLuint * arrays)) \ + _SG_XMACRO(glDepthMask, void, (GLboolean flag)) \ + _SG_XMACRO(glDrawArraysInstanced, void, (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)) \ + _SG_XMACRO(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) \ + _SG_XMACRO(glGenRenderbuffers, void, (GLsizei n, GLuint * renderbuffers)) \ + _SG_XMACRO(glBufferData, void, (GLenum target, GLsizeiptr size, const void * data, GLenum usage)) \ + _SG_XMACRO(glBlendFuncSeparate, void, (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) \ + _SG_XMACRO(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) \ + _SG_XMACRO(glGetIntegerv, void, (GLenum pname, GLint * data)) \ + _SG_XMACRO(glEnable, void, (GLenum cap)) \ + _SG_XMACRO(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) \ + _SG_XMACRO(glStencilMask, void, (GLuint mask)) \ + _SG_XMACRO(glAttachShader, void, (GLuint program, GLuint shader)) \ + _SG_XMACRO(glGetError, GLenum, (void)) \ + _SG_XMACRO(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) \ + _SG_XMACRO(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) \ + _SG_XMACRO(glTexParameterfv, void, (GLenum target, GLenum pname, const GLfloat* params)) \ + _SG_XMACRO(glGetShaderInfoLog, void, (GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog)) \ + _SG_XMACRO(glDepthFunc, void, (GLenum func)) \ + _SG_XMACRO(glStencilOp , void, (GLenum fail, GLenum zfail, GLenum zpass)) \ + _SG_XMACRO(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) \ + _SG_XMACRO(glEnableVertexAttribArray, void, (GLuint index)) \ + _SG_XMACRO(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) \ + _SG_XMACRO(glReadBuffer, void, (GLenum src)) \ + _SG_XMACRO(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels)) \ + _SG_XMACRO(glGenVertexArrays, void, (GLsizei n, GLuint * arrays)) \ + _SG_XMACRO(glFrontFace, void, (GLenum mode)) \ + _SG_XMACRO(glCullFace, void, (GLenum mode)) \ + _SG_XMACRO(glPixelStorei, void, (GLenum pname, GLint param)) \ + _SG_XMACRO(glBindSampler, void, (GLuint unit, GLuint sampler)) \ + _SG_XMACRO(glGenSamplers, void, (GLsizei n, GLuint* samplers)) \ + _SG_XMACRO(glSamplerParameteri, void, (GLuint sampler, GLenum pname, GLint param)) \ + _SG_XMACRO(glSamplerParameterf, void, (GLuint sampler, GLenum pname, GLfloat param)) \ + _SG_XMACRO(glSamplerParameterfv, void, (GLuint sampler, GLenum pname, const GLfloat* params)) \ + _SG_XMACRO(glDeleteSamplers, void, (GLsizei n, const GLuint* samplers)) \ + _SG_XMACRO(glBindBufferBase, void, (GLenum target, GLuint index, GLuint buffer)) \ + _SG_XMACRO(glTexImage2DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) \ + _SG_XMACRO(glTexImage3DMultisample, void, (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations)) \ + _SG_XMACRO(glDispatchCompute, void, (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z)) \ + _SG_XMACRO(glMemoryBarrier, void, (GLbitfield barriers)) + +// generate GL function pointer typedefs +#define _SG_XMACRO(name, ret, args) typedef ret (GL_APIENTRY* PFN_ ## name) args; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// generate GL function pointers +#define _SG_XMACRO(name, ret, args) static PFN_ ## name name; +_SG_GL_FUNCS +#undef _SG_XMACRO + +// helper function to lookup GL functions in GL DLL +typedef PROC (WINAPI * _sg_wglGetProcAddress)(LPCSTR); +_SOKOL_PRIVATE void* _sg_gl_getprocaddr(const char* name, _sg_wglGetProcAddress wgl_getprocaddress) { + void* proc_addr = (void*) wgl_getprocaddress(name); + if (0 == proc_addr) { + proc_addr = (void*) GetProcAddress(_sg.gl.opengl32_dll, name); + } + SOKOL_ASSERT(proc_addr); + return proc_addr; +} + +// populate GL function pointers +_SOKOL_PRIVATE void _sg_gl_load_opengl(void) { + SOKOL_ASSERT(0 == _sg.gl.opengl32_dll); + _sg.gl.opengl32_dll = LoadLibraryA("opengl32.dll"); + SOKOL_ASSERT(_sg.gl.opengl32_dll); + _sg_wglGetProcAddress wgl_getprocaddress = (_sg_wglGetProcAddress) GetProcAddress(_sg.gl.opengl32_dll, "wglGetProcAddress"); + SOKOL_ASSERT(wgl_getprocaddress); + #define _SG_XMACRO(name, ret, args) name = (PFN_ ## name) _sg_gl_getprocaddr(#name, wgl_getprocaddress); + _SG_GL_FUNCS + #undef _SG_XMACRO +} + +_SOKOL_PRIVATE void _sg_gl_unload_opengl(void) { + SOKOL_ASSERT(_sg.gl.opengl32_dll); + FreeLibrary(_sg.gl.opengl32_dll); + _sg.gl.opengl32_dll = 0; +} +#endif // _SOKOL_USE_WIN32_GL_LOADER + +//-- type translation ---------------------------------------------------------- +_SOKOL_PRIVATE GLenum _sg_gl_buffer_target(sg_buffer_type t) { + switch (t) { + case SG_BUFFERTYPE_VERTEXBUFFER: return GL_ARRAY_BUFFER; + case SG_BUFFERTYPE_INDEXBUFFER: return GL_ELEMENT_ARRAY_BUFFER; + case SG_BUFFERTYPE_STORAGEBUFFER: return GL_SHADER_STORAGE_BUFFER; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_texture_target(sg_image_type t, int sample_count) { + #if defined(SOKOL_GLCORE) + const bool msaa = sample_count > 1; + if (msaa) { + switch (t) { + case SG_IMAGETYPE_2D: return GL_TEXTURE_2D_MULTISAMPLE; + case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + default: SOKOL_UNREACHABLE; return 0; + } + } else { + switch (t) { + case SG_IMAGETYPE_2D: return GL_TEXTURE_2D; + case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP; + case SG_IMAGETYPE_3D: return GL_TEXTURE_3D; + case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_ARRAY; + default: SOKOL_UNREACHABLE; return 0; + } + } + #else + SOKOL_ASSERT(sample_count == 1); _SOKOL_UNUSED(sample_count); + switch (t) { + case SG_IMAGETYPE_2D: return GL_TEXTURE_2D; + case SG_IMAGETYPE_CUBE: return GL_TEXTURE_CUBE_MAP; + case SG_IMAGETYPE_3D: return GL_TEXTURE_3D; + case SG_IMAGETYPE_ARRAY: return GL_TEXTURE_2D_ARRAY; + default: SOKOL_UNREACHABLE; return 0; + } + #endif +} + +_SOKOL_PRIVATE GLenum _sg_gl_usage(sg_usage u) { + switch (u) { + case SG_USAGE_IMMUTABLE: return GL_STATIC_DRAW; + case SG_USAGE_DYNAMIC: return GL_DYNAMIC_DRAW; + case SG_USAGE_STREAM: return GL_STREAM_DRAW; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_shader_stage(sg_shader_stage stage) { + switch (stage) { + case SG_SHADERSTAGE_VERTEX: return GL_VERTEX_SHADER; + case SG_SHADERSTAGE_FRAGMENT: return GL_FRAGMENT_SHADER; + case SG_SHADERSTAGE_COMPUTE: return GL_COMPUTE_SHADER; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLint _sg_gl_vertexformat_size(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return 1; + case SG_VERTEXFORMAT_FLOAT2: return 2; + case SG_VERTEXFORMAT_FLOAT3: return 3; + case SG_VERTEXFORMAT_FLOAT4: return 4; + case SG_VERTEXFORMAT_INT: return 1; + case SG_VERTEXFORMAT_INT2: return 2; + case SG_VERTEXFORMAT_INT3: return 3; + case SG_VERTEXFORMAT_INT4: return 4; + case SG_VERTEXFORMAT_UINT: return 1; + case SG_VERTEXFORMAT_UINT2: return 2; + case SG_VERTEXFORMAT_UINT3: return 3; + case SG_VERTEXFORMAT_UINT4: return 4; + case SG_VERTEXFORMAT_BYTE4: return 4; + case SG_VERTEXFORMAT_BYTE4N: return 4; + case SG_VERTEXFORMAT_UBYTE4: return 4; + case SG_VERTEXFORMAT_UBYTE4N: return 4; + case SG_VERTEXFORMAT_SHORT2: return 2; + case SG_VERTEXFORMAT_SHORT2N: return 2; + case SG_VERTEXFORMAT_USHORT2: return 2; + case SG_VERTEXFORMAT_USHORT2N: return 2; + case SG_VERTEXFORMAT_SHORT4: return 4; + case SG_VERTEXFORMAT_SHORT4N: return 4; + case SG_VERTEXFORMAT_USHORT4: return 4; + case SG_VERTEXFORMAT_USHORT4N: return 4; + case SG_VERTEXFORMAT_UINT10_N2: return 4; + case SG_VERTEXFORMAT_HALF2: return 2; + case SG_VERTEXFORMAT_HALF4: return 4; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_vertexformat_type(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: + case SG_VERTEXFORMAT_FLOAT2: + case SG_VERTEXFORMAT_FLOAT3: + case SG_VERTEXFORMAT_FLOAT4: + return GL_FLOAT; + case SG_VERTEXFORMAT_INT: + case SG_VERTEXFORMAT_INT2: + case SG_VERTEXFORMAT_INT3: + case SG_VERTEXFORMAT_INT4: + return GL_INT; + case SG_VERTEXFORMAT_UINT: + case SG_VERTEXFORMAT_UINT2: + case SG_VERTEXFORMAT_UINT3: + case SG_VERTEXFORMAT_UINT4: + return GL_UNSIGNED_INT; + case SG_VERTEXFORMAT_BYTE4: + case SG_VERTEXFORMAT_BYTE4N: + return GL_BYTE; + case SG_VERTEXFORMAT_UBYTE4: + case SG_VERTEXFORMAT_UBYTE4N: + return GL_UNSIGNED_BYTE; + case SG_VERTEXFORMAT_SHORT2: + case SG_VERTEXFORMAT_SHORT2N: + case SG_VERTEXFORMAT_SHORT4: + case SG_VERTEXFORMAT_SHORT4N: + return GL_SHORT; + case SG_VERTEXFORMAT_USHORT2: + case SG_VERTEXFORMAT_USHORT2N: + case SG_VERTEXFORMAT_USHORT4: + case SG_VERTEXFORMAT_USHORT4N: + return GL_UNSIGNED_SHORT; + case SG_VERTEXFORMAT_UINT10_N2: + return GL_UNSIGNED_INT_2_10_10_10_REV; + case SG_VERTEXFORMAT_HALF2: + case SG_VERTEXFORMAT_HALF4: + return GL_HALF_FLOAT; + default: + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLboolean _sg_gl_vertexformat_normalized(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_BYTE4N: + case SG_VERTEXFORMAT_UBYTE4N: + case SG_VERTEXFORMAT_SHORT2N: + case SG_VERTEXFORMAT_USHORT2N: + case SG_VERTEXFORMAT_SHORT4N: + case SG_VERTEXFORMAT_USHORT4N: + case SG_VERTEXFORMAT_UINT10_N2: + return GL_TRUE; + default: + return GL_FALSE; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_primitive_type(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return GL_POINTS; + case SG_PRIMITIVETYPE_LINES: return GL_LINES; + case SG_PRIMITIVETYPE_LINE_STRIP: return GL_LINE_STRIP; + case SG_PRIMITIVETYPE_TRIANGLES: return GL_TRIANGLES; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_index_type(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_NONE: return 0; + case SG_INDEXTYPE_UINT16: return GL_UNSIGNED_SHORT; + case SG_INDEXTYPE_UINT32: return GL_UNSIGNED_INT; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_compare_func(sg_compare_func cmp) { + switch (cmp) { + case SG_COMPAREFUNC_NEVER: return GL_NEVER; + case SG_COMPAREFUNC_LESS: return GL_LESS; + case SG_COMPAREFUNC_EQUAL: return GL_EQUAL; + case SG_COMPAREFUNC_LESS_EQUAL: return GL_LEQUAL; + case SG_COMPAREFUNC_GREATER: return GL_GREATER; + case SG_COMPAREFUNC_NOT_EQUAL: return GL_NOTEQUAL; + case SG_COMPAREFUNC_GREATER_EQUAL: return GL_GEQUAL; + case SG_COMPAREFUNC_ALWAYS: return GL_ALWAYS; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_stencil_op(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return GL_KEEP; + case SG_STENCILOP_ZERO: return GL_ZERO; + case SG_STENCILOP_REPLACE: return GL_REPLACE; + case SG_STENCILOP_INCR_CLAMP: return GL_INCR; + case SG_STENCILOP_DECR_CLAMP: return GL_DECR; + case SG_STENCILOP_INVERT: return GL_INVERT; + case SG_STENCILOP_INCR_WRAP: return GL_INCR_WRAP; + case SG_STENCILOP_DECR_WRAP: return GL_DECR_WRAP; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_blend_factor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return GL_ZERO; + case SG_BLENDFACTOR_ONE: return GL_ONE; + case SG_BLENDFACTOR_SRC_COLOR: return GL_SRC_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR; + case SG_BLENDFACTOR_SRC_ALPHA: return GL_SRC_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; + case SG_BLENDFACTOR_DST_COLOR: return GL_DST_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return GL_ONE_MINUS_DST_COLOR; + case SG_BLENDFACTOR_DST_ALPHA: return GL_DST_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return GL_SRC_ALPHA_SATURATE; + case SG_BLENDFACTOR_BLEND_COLOR: return GL_CONSTANT_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return GL_ONE_MINUS_CONSTANT_COLOR; + case SG_BLENDFACTOR_BLEND_ALPHA: return GL_CONSTANT_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return GL_ONE_MINUS_CONSTANT_ALPHA; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_blend_op(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return GL_FUNC_ADD; + case SG_BLENDOP_SUBTRACT: return GL_FUNC_SUBTRACT; + case SG_BLENDOP_REVERSE_SUBTRACT: return GL_FUNC_REVERSE_SUBTRACT; + case SG_BLENDOP_MIN: return GL_MIN; + case SG_BLENDOP_MAX: return GL_MAX; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_min_filter(sg_filter min_f, sg_filter mipmap_f) { + if (min_f == SG_FILTER_NEAREST) { + switch (mipmap_f) { + case SG_FILTER_NEAREST: return GL_NEAREST_MIPMAP_NEAREST; + case SG_FILTER_LINEAR: return GL_NEAREST_MIPMAP_LINEAR; + default: SOKOL_UNREACHABLE; return (GLenum)0; + } + } else if (min_f == SG_FILTER_LINEAR) { + switch (mipmap_f) { + case SG_FILTER_NEAREST: return GL_LINEAR_MIPMAP_NEAREST; + case SG_FILTER_LINEAR: return GL_LINEAR_MIPMAP_LINEAR; + default: SOKOL_UNREACHABLE; return (GLenum)0; + } + } else { + SOKOL_UNREACHABLE; return (GLenum)0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_mag_filter(sg_filter mag_f) { + if (mag_f == SG_FILTER_NEAREST) { + return GL_NEAREST; + } else { + return GL_LINEAR; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_wrap(sg_wrap w) { + switch (w) { + case SG_WRAP_CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; + #if defined(SOKOL_GLCORE) + case SG_WRAP_CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; + #else + case SG_WRAP_CLAMP_TO_BORDER: return GL_CLAMP_TO_EDGE; + #endif + case SG_WRAP_REPEAT: return GL_REPEAT; + case SG_WRAP_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_type(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: + case SG_PIXELFORMAT_R8UI: + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_BGRA8: + return GL_UNSIGNED_BYTE; + case SG_PIXELFORMAT_R8SN: + case SG_PIXELFORMAT_R8SI: + case SG_PIXELFORMAT_RG8SN: + case SG_PIXELFORMAT_RG8SI: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA8SI: + return GL_BYTE; + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16UI: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16UI: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16UI: + return GL_UNSIGNED_SHORT; + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_R16SI: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_RGBA16SI: + return GL_SHORT; + case SG_PIXELFORMAT_R16F: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RGBA16F: + return GL_HALF_FLOAT; + case SG_PIXELFORMAT_R32UI: + case SG_PIXELFORMAT_RG32UI: + case SG_PIXELFORMAT_RGBA32UI: + return GL_UNSIGNED_INT; + case SG_PIXELFORMAT_R32SI: + case SG_PIXELFORMAT_RG32SI: + case SG_PIXELFORMAT_RGBA32SI: + return GL_INT; + case SG_PIXELFORMAT_R32F: + case SG_PIXELFORMAT_RG32F: + case SG_PIXELFORMAT_RGBA32F: + return GL_FLOAT; + case SG_PIXELFORMAT_RGB10A2: + return GL_UNSIGNED_INT_2_10_10_10_REV; + case SG_PIXELFORMAT_RG11B10F: + return GL_UNSIGNED_INT_10F_11F_11F_REV; + case SG_PIXELFORMAT_RGB9E5: + return GL_UNSIGNED_INT_5_9_9_9_REV; + case SG_PIXELFORMAT_DEPTH: + return GL_FLOAT; + case SG_PIXELFORMAT_DEPTH_STENCIL: + return GL_UNSIGNED_INT_24_8; + default: + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: + case SG_PIXELFORMAT_R8SN: + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_R16F: + case SG_PIXELFORMAT_R32F: + return GL_RED; + case SG_PIXELFORMAT_R8UI: + case SG_PIXELFORMAT_R8SI: + case SG_PIXELFORMAT_R16UI: + case SG_PIXELFORMAT_R16SI: + case SG_PIXELFORMAT_R32UI: + case SG_PIXELFORMAT_R32SI: + return GL_RED_INTEGER; + case SG_PIXELFORMAT_RG8: + case SG_PIXELFORMAT_RG8SN: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RG16F: + case SG_PIXELFORMAT_RG32F: + return GL_RG; + case SG_PIXELFORMAT_RG8UI: + case SG_PIXELFORMAT_RG8SI: + case SG_PIXELFORMAT_RG16UI: + case SG_PIXELFORMAT_RG16SI: + case SG_PIXELFORMAT_RG32UI: + case SG_PIXELFORMAT_RG32SI: + return GL_RG_INTEGER; + case SG_PIXELFORMAT_RGBA8: + case SG_PIXELFORMAT_SRGB8A8: + case SG_PIXELFORMAT_RGBA8SN: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: + case SG_PIXELFORMAT_RGBA16F: + case SG_PIXELFORMAT_RGBA32F: + case SG_PIXELFORMAT_RGB10A2: + return GL_RGBA; + case SG_PIXELFORMAT_RGBA8UI: + case SG_PIXELFORMAT_RGBA8SI: + case SG_PIXELFORMAT_RGBA16UI: + case SG_PIXELFORMAT_RGBA16SI: + case SG_PIXELFORMAT_RGBA32UI: + case SG_PIXELFORMAT_RGBA32SI: + return GL_RGBA_INTEGER; + case SG_PIXELFORMAT_RG11B10F: + case SG_PIXELFORMAT_RGB9E5: + return GL_RGB; + case SG_PIXELFORMAT_DEPTH: + return GL_DEPTH_COMPONENT; + case SG_PIXELFORMAT_DEPTH_STENCIL: + return GL_DEPTH_STENCIL; + case SG_PIXELFORMAT_BC1_RGBA: + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case SG_PIXELFORMAT_BC2_RGBA: + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case SG_PIXELFORMAT_BC3_RGBA: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC3_SRGBA: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC4_R: + return GL_COMPRESSED_RED_RGTC1; + case SG_PIXELFORMAT_BC4_RSN: + return GL_COMPRESSED_SIGNED_RED_RGTC1; + case SG_PIXELFORMAT_BC5_RG: + return GL_COMPRESSED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC5_RGSN: + return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC6H_RGBF: + return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC6H_RGBUF: + return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC7_RGBA: + return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_BC7_SRGBA: + return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_ETC2_RGB8: + return GL_COMPRESSED_RGB8_ETC2; + case SG_PIXELFORMAT_ETC2_SRGB8: + return GL_COMPRESSED_SRGB8_ETC2; + case SG_PIXELFORMAT_ETC2_RGB8A1: + return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + case SG_PIXELFORMAT_ETC2_RGBA8: + return GL_COMPRESSED_RGBA8_ETC2_EAC; + case SG_PIXELFORMAT_ETC2_SRGB8A8: + return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + case SG_PIXELFORMAT_EAC_R11: + return GL_COMPRESSED_R11_EAC; + case SG_PIXELFORMAT_EAC_R11SN: + return GL_COMPRESSED_SIGNED_R11_EAC; + case SG_PIXELFORMAT_EAC_RG11: + return GL_COMPRESSED_RG11_EAC; + case SG_PIXELFORMAT_EAC_RG11SN: + return GL_COMPRESSED_SIGNED_RG11_EAC; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + return GL_COMPRESSED_RGBA_ASTC_4x4_KHR; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + return GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR; + default: + SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_teximage_internal_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return GL_R8; + case SG_PIXELFORMAT_R8SN: return GL_R8_SNORM; + case SG_PIXELFORMAT_R8UI: return GL_R8UI; + case SG_PIXELFORMAT_R8SI: return GL_R8I; + #if !defined(SOKOL_GLES3) + case SG_PIXELFORMAT_R16: return GL_R16; + case SG_PIXELFORMAT_R16SN: return GL_R16_SNORM; + #endif + case SG_PIXELFORMAT_R16UI: return GL_R16UI; + case SG_PIXELFORMAT_R16SI: return GL_R16I; + case SG_PIXELFORMAT_R16F: return GL_R16F; + case SG_PIXELFORMAT_RG8: return GL_RG8; + case SG_PIXELFORMAT_RG8SN: return GL_RG8_SNORM; + case SG_PIXELFORMAT_RG8UI: return GL_RG8UI; + case SG_PIXELFORMAT_RG8SI: return GL_RG8I; + case SG_PIXELFORMAT_R32UI: return GL_R32UI; + case SG_PIXELFORMAT_R32SI: return GL_R32I; + case SG_PIXELFORMAT_R32F: return GL_R32F; + #if !defined(SOKOL_GLES3) + case SG_PIXELFORMAT_RG16: return GL_RG16; + case SG_PIXELFORMAT_RG16SN: return GL_RG16_SNORM; + #endif + case SG_PIXELFORMAT_RG16UI: return GL_RG16UI; + case SG_PIXELFORMAT_RG16SI: return GL_RG16I; + case SG_PIXELFORMAT_RG16F: return GL_RG16F; + case SG_PIXELFORMAT_RGBA8: return GL_RGBA8; + case SG_PIXELFORMAT_SRGB8A8: return GL_SRGB8_ALPHA8; + case SG_PIXELFORMAT_RGBA8SN: return GL_RGBA8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return GL_RGBA8UI; + case SG_PIXELFORMAT_RGBA8SI: return GL_RGBA8I; + case SG_PIXELFORMAT_RGB10A2: return GL_RGB10_A2; + case SG_PIXELFORMAT_RG11B10F: return GL_R11F_G11F_B10F; + case SG_PIXELFORMAT_RGB9E5: return GL_RGB9_E5; + case SG_PIXELFORMAT_RG32UI: return GL_RG32UI; + case SG_PIXELFORMAT_RG32SI: return GL_RG32I; + case SG_PIXELFORMAT_RG32F: return GL_RG32F; + #if !defined(SOKOL_GLES3) + case SG_PIXELFORMAT_RGBA16: return GL_RGBA16; + case SG_PIXELFORMAT_RGBA16SN: return GL_RGBA16_SNORM; + #endif + case SG_PIXELFORMAT_RGBA16UI: return GL_RGBA16UI; + case SG_PIXELFORMAT_RGBA16SI: return GL_RGBA16I; + case SG_PIXELFORMAT_RGBA16F: return GL_RGBA16F; + case SG_PIXELFORMAT_RGBA32UI: return GL_RGBA32UI; + case SG_PIXELFORMAT_RGBA32SI: return GL_RGBA32I; + case SG_PIXELFORMAT_RGBA32F: return GL_RGBA32F; + case SG_PIXELFORMAT_DEPTH: return GL_DEPTH_COMPONENT32F; + case SG_PIXELFORMAT_DEPTH_STENCIL: return GL_DEPTH24_STENCIL8; + case SG_PIXELFORMAT_BC1_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case SG_PIXELFORMAT_BC2_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case SG_PIXELFORMAT_BC3_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC3_SRGBA: return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + case SG_PIXELFORMAT_BC4_R: return GL_COMPRESSED_RED_RGTC1; + case SG_PIXELFORMAT_BC4_RSN: return GL_COMPRESSED_SIGNED_RED_RGTC1; + case SG_PIXELFORMAT_BC5_RG: return GL_COMPRESSED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC5_RGSN: return GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2; + case SG_PIXELFORMAT_BC6H_RGBF: return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC6H_RGBUF: return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; + case SG_PIXELFORMAT_BC7_RGBA: return GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_BC7_SRGBA: return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; + case SG_PIXELFORMAT_ETC2_RGB8: return GL_COMPRESSED_RGB8_ETC2; + case SG_PIXELFORMAT_ETC2_SRGB8: return GL_COMPRESSED_SRGB8_ETC2; + case SG_PIXELFORMAT_ETC2_RGB8A1: return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + case SG_PIXELFORMAT_ETC2_RGBA8: return GL_COMPRESSED_RGBA8_ETC2_EAC; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + case SG_PIXELFORMAT_EAC_R11: return GL_COMPRESSED_R11_EAC; + case SG_PIXELFORMAT_EAC_R11SN: return GL_COMPRESSED_SIGNED_R11_EAC; + case SG_PIXELFORMAT_EAC_RG11: return GL_COMPRESSED_RG11_EAC; + case SG_PIXELFORMAT_EAC_RG11SN: return GL_COMPRESSED_SIGNED_RG11_EAC; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return GL_COMPRESSED_RGBA_ASTC_4x4_KHR; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_cubeface_target(int face_index) { + switch (face_index) { + case 0: return GL_TEXTURE_CUBE_MAP_POSITIVE_X; + case 1: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X; + case 2: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y; + case 3: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; + case 4: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z; + case 5: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; + default: SOKOL_UNREACHABLE; return 0; + } +} + +// see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml +_SOKOL_PRIVATE void _sg_gl_init_pixelformats(bool has_bgra) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); + #if !defined(SOKOL_GLES3) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); + #if !defined(SOKOL_GLES3) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + if (has_bgra) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + } + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #if !defined(SOKOL_GLES3) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); +} + +// FIXME: OES_half_float_blend +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_half_float(bool has_colorbuffer_half_float) { + if (has_colorbuffer_half_float) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + } else { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + } +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_float(bool has_colorbuffer_float, bool has_texture_float_linear, bool has_float_blend) { + if (has_texture_float_linear) { + if (has_colorbuffer_float) { + if (has_float_blend) { + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } else { + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } + _sg_pixelformat_sfrm(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } else { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } + } else { + if (has_colorbuffer_float) { + _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sbrm(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } else { + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + _sg_pixelformat_s(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_s3tc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_rgtc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_bptc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_etc2(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]); +} + +_SOKOL_PRIVATE void _sg_gl_init_pixelformats_astc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]); + } + +_SOKOL_PRIVATE void _sg_gl_init_limits(void) { + _SG_GL_CHECK_ERROR(); + GLint gl_int; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_size_2d = gl_int; + _sg.limits.max_image_size_array = gl_int; + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_size_cube = gl_int; + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_int); + _SG_GL_CHECK_ERROR(); + if (gl_int > SG_MAX_VERTEX_ATTRIBUTES) { + gl_int = SG_MAX_VERTEX_ATTRIBUTES; + } + _sg.limits.max_vertex_attrs = gl_int; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.gl_max_vertex_uniform_components = gl_int; + glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_size_3d = gl_int; + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.max_image_array_layers = gl_int; + if (_sg.gl.ext_anisotropic) { + glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.gl.max_anisotropy = gl_int; + } else { + _sg.gl.max_anisotropy = 1; + } + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gl_int); + _SG_GL_CHECK_ERROR(); + _sg.limits.gl_max_combined_texture_image_units = gl_int; +} + +#if defined(SOKOL_GLCORE) +_SOKOL_PRIVATE void _sg_gl_init_caps_glcore(void) { + _sg.backend = SG_BACKEND_GLCORE; + + GLint major_version = 0; + GLint minor_version = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major_version); + glGetIntegerv(GL_MINOR_VERSION, &minor_version); + const int version = major_version * 100 + minor_version * 10; + _sg.features.origin_top_left = false; + _sg.features.image_clamp_to_border = true; + _sg.features.mrt_independent_blend_state = false; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = version >= 430; + #if defined(__APPLE__) + _sg.features.msaa_image_bindings = false; + #else + _sg.features.msaa_image_bindings = true; + #endif + + // scan extensions + bool has_s3tc = false; // BC1..BC3 + bool has_rgtc = false; // BC4 and BC5 + bool has_bptc = false; // BC6H and BC7 + bool has_etc2 = false; + bool has_astc = false; + GLint num_ext = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext); + for (int i = 0; i < num_ext; i++) { + const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i); + if (ext) { + if (strstr(ext, "_texture_compression_s3tc")) { + has_s3tc = true; + } else if (strstr(ext, "_texture_compression_rgtc")) { + has_rgtc = true; + } else if (strstr(ext, "_texture_compression_bptc")) { + has_bptc = true; + } else if (strstr(ext, "_ES3_compatibility")) { + has_etc2 = true; + } else if (strstr(ext, "_texture_filter_anisotropic")) { + _sg.gl.ext_anisotropic = true; + } else if (strstr(ext, "_texture_compression_astc_ldr")) { + has_astc = true; + } + } + } + + // limits + _sg_gl_init_limits(); + + // pixel formats + const bool has_bgra = false; // not a bug + const bool has_colorbuffer_float = true; + const bool has_colorbuffer_half_float = true; + const bool has_texture_float_linear = true; // FIXME??? + const bool has_float_blend = true; + _sg_gl_init_pixelformats(has_bgra); + _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); + _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float); + if (has_s3tc) { + _sg_gl_init_pixelformats_s3tc(); + } + if (has_rgtc) { + _sg_gl_init_pixelformats_rgtc(); + } + if (has_bptc) { + _sg_gl_init_pixelformats_bptc(); + } + if (has_etc2) { + _sg_gl_init_pixelformats_etc2(); + } + if (has_astc) { + _sg_gl_init_pixelformats_astc(); + } +} +#endif + +#if defined(SOKOL_GLES3) +_SOKOL_PRIVATE void _sg_gl_init_caps_gles3(void) { + _sg.backend = SG_BACKEND_GLES3; + + GLint major_version = 0; + GLint minor_version = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major_version); + glGetIntegerv(GL_MINOR_VERSION, &minor_version); + const int version = major_version * 100 + minor_version * 10; + _sg.features.origin_top_left = false; + _sg.features.image_clamp_to_border = false; + _sg.features.mrt_independent_blend_state = false; + _sg.features.mrt_independent_write_mask = false; + _sg.features.compute = version >= 310; + _sg.features.msaa_image_bindings = false; + + bool has_s3tc = false; // BC1..BC3 + bool has_rgtc = false; // BC4 and BC5 + bool has_bptc = false; // BC6H and BC7 + #if defined(__EMSCRIPTEN__) + bool has_etc2 = false; + #else + bool has_etc2 = true; + #endif + bool has_astc = false; + bool has_colorbuffer_float = false; + bool has_colorbuffer_half_float = false; + bool has_texture_float_linear = false; + bool has_float_blend = false; + GLint num_ext = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_ext); + for (int i = 0; i < num_ext; i++) { + const char* ext = (const char*) glGetStringi(GL_EXTENSIONS, (GLuint)i); + if (ext) { + if (strstr(ext, "_texture_compression_s3tc")) { + has_s3tc = true; + } else if (strstr(ext, "_compressed_texture_s3tc")) { + has_s3tc = true; + } else if (strstr(ext, "_texture_compression_rgtc")) { + has_rgtc = true; + } else if (strstr(ext, "_texture_compression_bptc")) { + has_bptc = true; + } else if (strstr(ext, "_compressed_texture_etc")) { + has_etc2 = true; + } else if (strstr(ext, "_compressed_texture_astc")) { + has_astc = true; + } else if (strstr(ext, "_color_buffer_float")) { + has_colorbuffer_float = true; + } else if (strstr(ext, "_color_buffer_half_float")) { + has_colorbuffer_half_float = true; + } else if (strstr(ext, "_texture_float_linear")) { + has_texture_float_linear = true; + } else if (strstr(ext, "_float_blend")) { + has_float_blend = true; + } else if (strstr(ext, "_texture_filter_anisotropic")) { + _sg.gl.ext_anisotropic = true; + } + } + } + + /* on WebGL2, color_buffer_float also includes 16-bit formats + see: https://developer.mozilla.org/en-US/docs/Web/API/EXT_color_buffer_float + */ + #if defined(__EMSCRIPTEN__) + if (!has_colorbuffer_half_float && has_colorbuffer_float) { + has_colorbuffer_half_float = has_colorbuffer_float; + } + #endif + + // limits + _sg_gl_init_limits(); + + // pixel formats + const bool has_bgra = false; // not a bug + _sg_gl_init_pixelformats(has_bgra); + _sg_gl_init_pixelformats_float(has_colorbuffer_float, has_texture_float_linear, has_float_blend); + _sg_gl_init_pixelformats_half_float(has_colorbuffer_half_float); + if (has_s3tc) { + _sg_gl_init_pixelformats_s3tc(); + } + if (has_rgtc) { + _sg_gl_init_pixelformats_rgtc(); + } + if (has_bptc) { + _sg_gl_init_pixelformats_bptc(); + } + if (has_etc2) { + _sg_gl_init_pixelformats_etc2(); + } + if (has_astc) { + _sg_gl_init_pixelformats_astc(); + } +} +#endif + +//-- state cache implementation ------------------------------------------------ +_SOKOL_PRIVATE void _sg_gl_cache_clear_buffer_bindings(bool force) { + if (force || (_sg.gl.cache.vertex_buffer != 0)) { + glBindBuffer(GL_ARRAY_BUFFER, 0); + _sg.gl.cache.vertex_buffer = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (force || (_sg.gl.cache.index_buffer != 0)) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + _sg.gl.cache.index_buffer = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (force || (_sg.gl.cache.storage_buffer != 0)) { + if (_sg.features.compute) { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } + _sg.gl.cache.storage_buffer = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + for (size_t i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) { + if (force || (_sg.gl.cache.storage_buffers[i] != 0)) { + if (_sg.features.compute) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, 0); + } + _sg.gl.cache.storage_buffers[i] = 0; + _sg_stats_add(gl.num_bind_buffer, 1); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_buffer(GLenum target, GLuint buffer) { + SOKOL_ASSERT((GL_ARRAY_BUFFER == target) || (GL_ELEMENT_ARRAY_BUFFER == target) || (GL_SHADER_STORAGE_BUFFER == target)); + if (target == GL_ARRAY_BUFFER) { + if (_sg.gl.cache.vertex_buffer != buffer) { + _sg.gl.cache.vertex_buffer = buffer; + glBindBuffer(target, buffer); + _sg_stats_add(gl.num_bind_buffer, 1); + } + } else if (target == GL_ELEMENT_ARRAY_BUFFER) { + if (_sg.gl.cache.index_buffer != buffer) { + _sg.gl.cache.index_buffer = buffer; + glBindBuffer(target, buffer); + _sg_stats_add(gl.num_bind_buffer, 1); + } + } else if (target == GL_SHADER_STORAGE_BUFFER) { + if (_sg.gl.cache.storage_buffer != buffer) { + _sg.gl.cache.storage_buffer = buffer; + if (_sg.features.compute) { + glBindBuffer(target, buffer); + } + _sg_stats_add(gl.num_bind_buffer, 1); + } + } else { + SOKOL_UNREACHABLE; + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_storage_buffer(uint8_t glsl_binding_n, GLuint buffer) { + SOKOL_ASSERT(glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS); + if (_sg.gl.cache.storage_buffers[glsl_binding_n] != buffer) { + _sg.gl.cache.storage_buffers[glsl_binding_n] = buffer; + _sg.gl.cache.storage_buffer = buffer; // not a bug + if (_sg.features.compute) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, glsl_binding_n, buffer); + } + _sg_stats_add(gl.num_bind_buffer, 1); + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_store_buffer_binding(GLenum target) { + if (target == GL_ARRAY_BUFFER) { + _sg.gl.cache.stored_vertex_buffer = _sg.gl.cache.vertex_buffer; + } else if (target == GL_ELEMENT_ARRAY_BUFFER) { + _sg.gl.cache.stored_index_buffer = _sg.gl.cache.index_buffer; + } else if (target == GL_SHADER_STORAGE_BUFFER) { + _sg.gl.cache.stored_storage_buffer = _sg.gl.cache.storage_buffer; + } else { + SOKOL_UNREACHABLE; + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_buffer_binding(GLenum target) { + if (target == GL_ARRAY_BUFFER) { + if (_sg.gl.cache.stored_vertex_buffer != 0) { + // we only care about restoring valid ids + _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_vertex_buffer); + _sg.gl.cache.stored_vertex_buffer = 0; + } + } else if (target == GL_ELEMENT_ARRAY_BUFFER) { + if (_sg.gl.cache.stored_index_buffer != 0) { + // we only care about restoring valid ids + _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_index_buffer); + _sg.gl.cache.stored_index_buffer = 0; + } + } else if (target == GL_SHADER_STORAGE_BUFFER) { + if (_sg.gl.cache.stored_storage_buffer != 0) { + // we only care about restoring valid ids + _sg_gl_cache_bind_buffer(target, _sg.gl.cache.stored_storage_buffer); + _sg.gl.cache.stored_storage_buffer = 0; + } + } else { + SOKOL_UNREACHABLE; + } +} + +// called from _sg_gl_discard_buffer() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_buffer(GLuint buf) { + if (buf == _sg.gl.cache.vertex_buffer) { + _sg.gl.cache.vertex_buffer = 0; + glBindBuffer(GL_ARRAY_BUFFER, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (buf == _sg.gl.cache.index_buffer) { + _sg.gl.cache.index_buffer = 0; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + if (buf == _sg.gl.cache.storage_buffer) { + _sg.gl.cache.storage_buffer = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + for (size_t i = 0; i < _SG_GL_MAX_SBUF_BINDINGS; i++) { + if (buf == _sg.gl.cache.storage_buffers[i]) { + _sg.gl.cache.storage_buffers[i] = 0; + _sg.gl.cache.storage_buffer = 0; // not a bug! + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, 0); + _sg_stats_add(gl.num_bind_buffer, 1); + } + } + if (buf == _sg.gl.cache.stored_vertex_buffer) { + _sg.gl.cache.stored_vertex_buffer = 0; + } + if (buf == _sg.gl.cache.stored_index_buffer) { + _sg.gl.cache.stored_index_buffer = 0; + } + if (buf == _sg.gl.cache.stored_storage_buffer) { + _sg.gl.cache.stored_storage_buffer = 0; + } + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + if (buf == _sg.gl.cache.attrs[i].gl_vbuf) { + _sg.gl.cache.attrs[i].gl_vbuf = 0; + } + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_active_texture(GLenum texture) { + _SG_GL_CHECK_ERROR(); + if (_sg.gl.cache.cur_active_texture != texture) { + _sg.gl.cache.cur_active_texture = texture; + glActiveTexture(texture); + _sg_stats_add(gl.num_active_texture, 1); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_cache_clear_texture_sampler_bindings(bool force) { + _SG_GL_CHECK_ERROR(); + for (int i = 0; (i < _SG_GL_MAX_IMG_SMP_BINDINGS) && (i < _sg.limits.gl_max_combined_texture_image_units); i++) { + if (force || (_sg.gl.cache.texture_samplers[i].texture != 0)) { + GLenum gl_texture_unit = (GLenum) (GL_TEXTURE0 + i); + glActiveTexture(gl_texture_unit); + _sg_stats_add(gl.num_active_texture, 1); + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glBindTexture(GL_TEXTURE_3D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + _sg_stats_add(gl.num_bind_texture, 4); + glBindSampler((GLuint)i, 0); + _sg_stats_add(gl.num_bind_sampler, 1); + _sg.gl.cache.texture_samplers[i].target = 0; + _sg.gl.cache.texture_samplers[i].texture = 0; + _sg.gl.cache.texture_samplers[i].sampler = 0; + _sg.gl.cache.cur_active_texture = gl_texture_unit; + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_texture_sampler(int8_t gl_tex_slot, GLenum target, GLuint texture, GLuint sampler) { + /* it's valid to call this function with target=0 and/or texture=0 + target=0 will unbind the previous binding, texture=0 will clear + the new binding + */ + SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_IMG_SMP_BINDINGS)); + if (gl_tex_slot >= _sg.limits.gl_max_combined_texture_image_units) { + return; + } + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.texture_samplers[gl_tex_slot]; + if ((slot->target != target) || (slot->texture != texture) || (slot->sampler != sampler)) { + _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + gl_tex_slot)); + // if the target has changed, clear the previous binding on that target + if ((target != slot->target) && (slot->target != 0)) { + glBindTexture(slot->target, 0); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_texture, 1); + } + // apply new binding (can be 0 to unbind) + if (target != 0) { + glBindTexture(target, texture); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_texture, 1); + } + // apply new sampler (can be 0 to unbind) + glBindSampler((GLuint)gl_tex_slot, sampler); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_sampler, 1); + + slot->target = target; + slot->texture = texture; + slot->sampler = sampler; + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_store_texture_sampler_binding(int8_t gl_tex_slot) { + SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_IMG_SMP_BINDINGS)); + _sg.gl.cache.stored_texture_sampler = _sg.gl.cache.texture_samplers[gl_tex_slot]; +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_texture_sampler_binding(int8_t gl_tex_slot) { + SOKOL_ASSERT((gl_tex_slot >= 0) && (gl_tex_slot < _SG_GL_MAX_IMG_SMP_BINDINGS)); + _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.stored_texture_sampler; + if (slot->texture != 0) { + // we only care about restoring valid ids + SOKOL_ASSERT(slot->target != 0); + _sg_gl_cache_bind_texture_sampler(gl_tex_slot, slot->target, slot->texture, slot->sampler); + slot->target = 0; + slot->texture = 0; + slot->sampler = 0; + } +} + +// called from _sg_gl_discard_texture() and _sg_gl_discard_sampler() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_texture_sampler(GLuint tex, GLuint smp) { + _SG_GL_CHECK_ERROR(); + for (size_t i = 0; i < _SG_GL_MAX_IMG_SMP_BINDINGS; i++) { + _sg_gl_cache_texture_sampler_bind_slot* slot = &_sg.gl.cache.texture_samplers[i]; + if ((0 != slot->target) && ((tex == slot->texture) || (smp == slot->sampler))) { + _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + i)); + glBindTexture(slot->target, 0); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_texture, 1); + glBindSampler((GLuint)i, 0); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_bind_sampler, 1); + slot->target = 0; + slot->texture = 0; + slot->sampler = 0; + } + } + if ((tex == _sg.gl.cache.stored_texture_sampler.texture) || (smp == _sg.gl.cache.stored_texture_sampler.sampler)) { + _sg.gl.cache.stored_texture_sampler.target = 0; + _sg.gl.cache.stored_texture_sampler.texture = 0; + _sg.gl.cache.stored_texture_sampler.sampler = 0; + } +} + +// called from _sg_gl_discard_shader() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_program(GLuint prog) { + if (prog == _sg.gl.cache.prog) { + _sg.gl.cache.prog = 0; + glUseProgram(0); + _sg_stats_add(gl.num_use_program, 1); + } +} + +// called from _sg_gl_discard_pipeline() +_SOKOL_PRIVATE void _sg_gl_cache_invalidate_pipeline(_sg_pipeline_t* pip) { + if (pip == _sg.gl.cache.cur_pipeline) { + _sg.gl.cache.cur_pipeline = 0; + _sg.gl.cache.cur_pipeline_id.id = SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_gl_reset_state_cache(void) { + _SG_GL_CHECK_ERROR(); + glBindVertexArray(_sg.gl.vao); + _SG_GL_CHECK_ERROR(); + _sg_clear(&_sg.gl.cache, sizeof(_sg.gl.cache)); + _sg_gl_cache_clear_buffer_bindings(true); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_clear_texture_sampler_bindings(true); + _SG_GL_CHECK_ERROR(); + for (int i = 0; i < _sg.limits.max_vertex_attrs; i++) { + _sg_gl_attr_t* attr = &_sg.gl.cache.attrs[i].gl_attr; + attr->vb_index = -1; + attr->divisor = -1; + glDisableVertexAttribArray((GLuint)i); + _SG_GL_CHECK_ERROR(); + _sg_stats_add(gl.num_disable_vertex_attrib_array, 1); + } + _sg.gl.cache.cur_primitive_type = GL_TRIANGLES; + + // shader program + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&_sg.gl.cache.prog); + _SG_GL_CHECK_ERROR(); + + // depth and stencil state + _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS; + _sg.gl.cache.stencil.front.compare = SG_COMPAREFUNC_ALWAYS; + _sg.gl.cache.stencil.front.fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.front.depth_fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.front.pass_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.back.compare = SG_COMPAREFUNC_ALWAYS; + _sg.gl.cache.stencil.back.fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.back.depth_fail_op = SG_STENCILOP_KEEP; + _sg.gl.cache.stencil.back.pass_op = SG_STENCILOP_KEEP; + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_ALWAYS); + glDepthMask(GL_FALSE); + glDisable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 0, 0); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilMask(0); + _sg_stats_add(gl.num_render_state, 7); + + // blend state + _sg.gl.cache.blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + _sg.gl.cache.blend.dst_factor_rgb = SG_BLENDFACTOR_ZERO; + _sg.gl.cache.blend.op_rgb = SG_BLENDOP_ADD; + _sg.gl.cache.blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + _sg.gl.cache.blend.dst_factor_alpha = SG_BLENDFACTOR_ZERO; + _sg.gl.cache.blend.op_alpha = SG_BLENDOP_ADD; + glDisable(GL_BLEND); + glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO); + glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); + glBlendColor(0.0f, 0.0f, 0.0f, 0.0f); + _sg_stats_add(gl.num_render_state, 4); + + // standalone state + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA; + } + _sg.gl.cache.cull_mode = SG_CULLMODE_NONE; + _sg.gl.cache.face_winding = SG_FACEWINDING_CW; + _sg.gl.cache.sample_count = 1; + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glPolygonOffset(0.0f, 0.0f); + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_CULL_FACE); + glFrontFace(GL_CW); + glCullFace(GL_BACK); + glEnable(GL_SCISSOR_TEST); + glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); + glEnable(GL_DITHER); + glDisable(GL_POLYGON_OFFSET_FILL); + _sg_stats_add(gl.num_render_state, 10); + #if defined(SOKOL_GLCORE) + glEnable(GL_MULTISAMPLE); + glEnable(GL_PROGRAM_POINT_SIZE); + _sg_stats_add(gl.num_render_state, 2); + #endif +} + +_SOKOL_PRIVATE void _sg_gl_setup_backend(const sg_desc* desc) { + _SOKOL_UNUSED(desc); + + // assumes that _sg.gl is already zero-initialized + _sg.gl.valid = true; + + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + _sg_gl_load_opengl(); + #endif + + // clear initial GL error state + #if defined(SOKOL_DEBUG) + while (glGetError() != GL_NO_ERROR); + #endif + #if defined(SOKOL_GLCORE) + _sg_gl_init_caps_glcore(); + #elif defined(SOKOL_GLES3) + _sg_gl_init_caps_gles3(); + #endif + + glGenVertexArrays(1, &_sg.gl.vao); + glBindVertexArray(_sg.gl.vao); + _SG_GL_CHECK_ERROR(); + // incoming texture data is generally expected to be packed tightly + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + #if defined(SOKOL_GLCORE) + // enable seamless cubemap sampling (only desktop GL) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + #endif + _sg_gl_reset_state_cache(); +} + +_SOKOL_PRIVATE void _sg_gl_discard_backend(void) { + SOKOL_ASSERT(_sg.gl.valid); + if (_sg.gl.vao) { + glDeleteVertexArrays(1, &_sg.gl.vao); + } + #if defined(_SOKOL_USE_WIN32_GL_LOADER) + _sg_gl_unload_opengl(); + #endif + _sg.gl.valid = false; +} + +//-- GL backend resource creation and destruction ------------------------------ +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + _SG_GL_CHECK_ERROR(); + buf->gl.injected = (0 != desc->gl_buffers[0]); + const GLenum gl_target = _sg_gl_buffer_target(buf->cmn.type); + const GLenum gl_usage = _sg_gl_usage(buf->cmn.usage); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + GLuint gl_buf = 0; + if (buf->gl.injected) { + SOKOL_ASSERT(desc->gl_buffers[slot]); + gl_buf = desc->gl_buffers[slot]; + } else { + glGenBuffers(1, &gl_buf); + SOKOL_ASSERT(gl_buf); + _sg_gl_cache_store_buffer_binding(gl_target); + _sg_gl_cache_bind_buffer(gl_target, gl_buf); + glBufferData(gl_target, buf->cmn.size, 0, gl_usage); + if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { + if (desc->data.ptr) { + glBufferSubData(gl_target, 0, buf->cmn.size, desc->data.ptr); + } else { + // setup a zero-initialized buffer (don't explicitly need to do this on WebGL) + #if !defined(__EMSCRIPTEN__) + void* ptr = _sg_malloc_clear((size_t)buf->cmn.size); + glBufferSubData(gl_target, 0, buf->cmn.size, ptr); + _sg_free(ptr); + #endif + } + } + _sg_gl_cache_restore_buffer_binding(gl_target); + } + buf->gl.buf[slot] = gl_buf; + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _SG_GL_CHECK_ERROR(); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + if (buf->gl.buf[slot]) { + _sg_gl_cache_invalidate_buffer(buf->gl.buf[slot]); + if (!buf->gl.injected) { + glDeleteBuffers(1, &buf->gl.buf[slot]); + } + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE bool _sg_gl_supported_texture_format(sg_pixel_format fmt) { + const int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM)); + return _sg.formats[fmt_index].sample; +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + _SG_GL_CHECK_ERROR(); + const bool msaa = img->cmn.sample_count > 1; + img->gl.injected = (0 != desc->gl_textures[0]); + + // check if texture format is support + if (!_sg_gl_supported_texture_format(img->cmn.pixel_format)) { + _SG_ERROR(GL_TEXTURE_FORMAT_NOT_SUPPORTED); + return SG_RESOURCESTATE_FAILED; + } + const GLenum gl_internal_format = _sg_gl_teximage_internal_format(img->cmn.pixel_format); + + // GLES3/WebGL2/macOS doesn't have support for multisampled textures, so create a render buffer object instead + if (!_sg.features.msaa_image_bindings && img->cmn.render_target && msaa) { + glGenRenderbuffers(1, &img->gl.msaa_render_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, img->gl.msaa_render_buffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, img->cmn.sample_count, gl_internal_format, img->cmn.width, img->cmn.height); + } else if (img->gl.injected) { + img->gl.target = _sg_gl_texture_target(img->cmn.type, img->cmn.sample_count); + // inject externally GL textures + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + SOKOL_ASSERT(desc->gl_textures[slot]); + img->gl.tex[slot] = desc->gl_textures[slot]; + } + if (desc->gl_texture_target) { + img->gl.target = (GLenum)desc->gl_texture_target; + } + } else { + // create our own GL texture(s) + img->gl.target = _sg_gl_texture_target(img->cmn.type, img->cmn.sample_count); + const GLenum gl_format = _sg_gl_teximage_format(img->cmn.pixel_format); + const bool is_compressed = _sg_is_compressed_pixel_format(img->cmn.pixel_format); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + glGenTextures(1, &img->gl.tex[slot]); + SOKOL_ASSERT(img->gl.tex[slot]); + _sg_gl_cache_store_texture_sampler_binding(0); + _sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[slot], 0); + glTexParameteri(img->gl.target, GL_TEXTURE_MAX_LEVEL, img->cmn.num_mipmaps - 1); + + // NOTE: workaround for https://issues.chromium.org/issues/355605685 + // FIXME: on GLES3 and GL 4.3 (e.g. not macOS) the texture initialization + // should be rewritten to use glTexStorage + glTexSubImage + bool tex_storage_allocated = false; + #if defined(__EMSCRIPTEN__) + if (desc->data.subimage[0][0].ptr == 0) { + SOKOL_ASSERT(!msaa); + tex_storage_allocated = true; + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + glTexStorage2D(img->gl.target, img->cmn.num_mipmaps, gl_internal_format, img->cmn.width, img->cmn.height); + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + glTexStorage3D(img->gl.target, img->cmn.num_mipmaps, gl_internal_format, img->cmn.width, img->cmn.height, img->cmn.num_slices); + } + } + #endif + if (!tex_storage_allocated) { + const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1; + int data_index = 0; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, data_index++) { + GLenum gl_img_target = img->gl.target; + if (SG_IMAGETYPE_CUBE == img->cmn.type) { + gl_img_target = _sg_gl_cubeface_target(face_index); + } + const GLvoid* data_ptr = desc->data.subimage[face_index][mip_index].ptr; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + if (is_compressed) { + SOKOL_ASSERT(!msaa); + const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; + glCompressedTexImage2D(gl_img_target, mip_index, gl_internal_format, + mip_width, mip_height, 0, data_size, data_ptr); + } else { + const GLenum gl_type = _sg_gl_teximage_type(img->cmn.pixel_format); + #if defined(SOKOL_GLCORE) && !defined(__APPLE__) + if (msaa) { + glTexImage2DMultisample(gl_img_target, img->cmn.sample_count, gl_internal_format, + mip_width, mip_height, GL_TRUE); + } else { + glTexImage2D(gl_img_target, mip_index, (GLint)gl_internal_format, + mip_width, mip_height, 0, gl_format, gl_type, data_ptr); + } + #else + SOKOL_ASSERT(!msaa); + glTexImage2D(gl_img_target, mip_index, (GLint)gl_internal_format, + mip_width, mip_height, 0, gl_format, gl_type, data_ptr); + #endif + } + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + int mip_depth = img->cmn.num_slices; + if (SG_IMAGETYPE_3D == img->cmn.type) { + mip_depth = _sg_miplevel_dim(mip_depth, mip_index); + } + if (is_compressed) { + SOKOL_ASSERT(!msaa); + const GLsizei data_size = (GLsizei) desc->data.subimage[face_index][mip_index].size; + glCompressedTexImage3D(gl_img_target, mip_index, gl_internal_format, + mip_width, mip_height, mip_depth, 0, data_size, data_ptr); + } else { + const GLenum gl_type = _sg_gl_teximage_type(img->cmn.pixel_format); + #if defined(SOKOL_GLCORE) && !defined(__APPLE__) + if (msaa) { + // NOTE: only for array textures, not actual 3D textures! + glTexImage3DMultisample(gl_img_target, img->cmn.sample_count, gl_internal_format, + mip_width, mip_height, mip_depth, GL_TRUE); + } else { + glTexImage3D(gl_img_target, mip_index, (GLint)gl_internal_format, + mip_width, mip_height, mip_depth, 0, gl_format, gl_type, data_ptr); + } + #else + SOKOL_ASSERT(!msaa); + glTexImage3D(gl_img_target, mip_index, (GLint)gl_internal_format, + mip_width, mip_height, mip_depth, 0, gl_format, gl_type, data_ptr); + #endif + } + } + } + } + } + _sg_gl_cache_restore_texture_sampler_binding(0); + } + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _SG_GL_CHECK_ERROR(); + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + if (img->gl.tex[slot]) { + _sg_gl_cache_invalidate_texture_sampler(img->gl.tex[slot], 0); + if (!img->gl.injected) { + glDeleteTextures(1, &img->gl.tex[slot]); + } + } + } + if (img->gl.msaa_render_buffer) { + glDeleteRenderbuffers(1, &img->gl.msaa_render_buffer); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + _SG_GL_CHECK_ERROR(); + smp->gl.injected = (0 != desc->gl_sampler); + if (smp->gl.injected) { + smp->gl.smp = (GLuint) desc->gl_sampler; + } else { + glGenSamplers(1, &smp->gl.smp); + SOKOL_ASSERT(smp->gl.smp); + + const GLenum gl_min_filter = _sg_gl_min_filter(smp->cmn.min_filter, smp->cmn.mipmap_filter); + const GLenum gl_mag_filter = _sg_gl_mag_filter(smp->cmn.mag_filter); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MIN_FILTER, (GLint)gl_min_filter); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MAG_FILTER, (GLint)gl_mag_filter); + // GL spec has strange defaults for mipmap min/max lod: -1000 to +1000 + const float min_lod = _sg_clamp(desc->min_lod, 0.0f, 1000.0f); + const float max_lod = _sg_clamp(desc->max_lod, 0.0f, 1000.0f); + glSamplerParameterf(smp->gl.smp, GL_TEXTURE_MIN_LOD, min_lod); + glSamplerParameterf(smp->gl.smp, GL_TEXTURE_MAX_LOD, max_lod); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_S, (GLint)_sg_gl_wrap(smp->cmn.wrap_u)); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_T, (GLint)_sg_gl_wrap(smp->cmn.wrap_v)); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_WRAP_R, (GLint)_sg_gl_wrap(smp->cmn.wrap_w)); + #if defined(SOKOL_GLCORE) + float border[4]; + switch (smp->cmn.border_color) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: + border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 0.0f; + break; + case SG_BORDERCOLOR_OPAQUE_WHITE: + border[0] = 1.0f; border[1] = 1.0f; border[2] = 1.0f; border[3] = 1.0f; + break; + default: + border[0] = 0.0f; border[1] = 0.0f; border[2] = 0.0f; border[3] = 1.0f; + break; + } + glSamplerParameterfv(smp->gl.smp, GL_TEXTURE_BORDER_COLOR, border); + #endif + if (smp->cmn.compare != SG_COMPAREFUNC_NEVER) { + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_FUNC, (GLint)_sg_gl_compare_func(smp->cmn.compare)); + } else { + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + if (_sg.gl.ext_anisotropic && (smp->cmn.max_anisotropy > 1)) { + GLint max_aniso = (GLint) smp->cmn.max_anisotropy; + if (max_aniso > _sg.gl.max_anisotropy) { + max_aniso = _sg.gl.max_anisotropy; + } + glSamplerParameteri(smp->gl.smp, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_aniso); + } + } + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_invalidate_texture_sampler(0, smp->gl.smp); + if (!smp->gl.injected) { + glDeleteSamplers(1, &smp->gl.smp); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE GLuint _sg_gl_compile_shader(sg_shader_stage stage, const char* src) { + SOKOL_ASSERT(src); + _SG_GL_CHECK_ERROR(); + GLuint gl_shd = glCreateShader(_sg_gl_shader_stage(stage)); + glShaderSource(gl_shd, 1, &src, 0); + glCompileShader(gl_shd); + GLint compile_status = 0; + glGetShaderiv(gl_shd, GL_COMPILE_STATUS, &compile_status); + if (!compile_status) { + // compilation failed, log error and delete shader + GLint log_len = 0; + glGetShaderiv(gl_shd, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 0) { + GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); + glGetShaderInfoLog(gl_shd, log_len, &log_len, log_buf); + _SG_ERROR(GL_SHADER_COMPILATION_FAILED); + _SG_LOGMSG(GL_SHADER_COMPILATION_FAILED, log_buf); + _sg_free(log_buf); + } + glDeleteShader(gl_shd); + gl_shd = 0; + } + _SG_GL_CHECK_ERROR(); + return gl_shd; +} + +// NOTE: this is an out-of-range check for GLSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_gl_ensure_glsl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].glsl_binding_n >= _SG_GL_MAX_SBUF_BINDINGS) { + _SG_ERROR(GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE); + return false; + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(!shd->gl.prog); + _SG_GL_CHECK_ERROR(); + + // perform a fatal range-check on GLSL bindslots that's also active + // in release mode to avoid potential out-of-bounds array accesses + if (!_sg_gl_ensure_glsl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + // copy the optional vertex attribute names over + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + _sg_strcpy(&shd->gl.attrs[i].name, desc->attrs[i].glsl_name); + } + + const bool has_vs = desc->vertex_func.source; + const bool has_fs = desc->fragment_func.source; + const bool has_cs = desc->compute_func.source; + SOKOL_ASSERT((has_vs && has_fs) || has_cs); + GLuint gl_prog = glCreateProgram(); + if (has_vs && has_fs) { + GLuint gl_vs = _sg_gl_compile_shader(SG_SHADERSTAGE_VERTEX, desc->vertex_func.source); + GLuint gl_fs = _sg_gl_compile_shader(SG_SHADERSTAGE_FRAGMENT, desc->fragment_func.source); + if (!(gl_vs && gl_fs)) { + glDeleteProgram(gl_prog); + if (gl_vs) { glDeleteShader(gl_vs); } + if (gl_fs) { glDeleteShader(gl_fs); } + return SG_RESOURCESTATE_FAILED; + } + glAttachShader(gl_prog, gl_vs); + glAttachShader(gl_prog, gl_fs); + glLinkProgram(gl_prog); + glDeleteShader(gl_vs); + glDeleteShader(gl_fs); + _SG_GL_CHECK_ERROR(); + } else if (has_cs) { + GLuint gl_cs = _sg_gl_compile_shader(SG_SHADERSTAGE_COMPUTE, desc->compute_func.source); + if (!gl_cs) { + glDeleteProgram(gl_prog); + return SG_RESOURCESTATE_FAILED; + } + glAttachShader(gl_prog, gl_cs); + glLinkProgram(gl_prog); + glDeleteShader(gl_cs); + _SG_GL_CHECK_ERROR(); + } else { + SOKOL_UNREACHABLE; + } + GLint link_status; + glGetProgramiv(gl_prog, GL_LINK_STATUS, &link_status); + if (!link_status) { + GLint log_len = 0; + glGetProgramiv(gl_prog, GL_INFO_LOG_LENGTH, &log_len); + if (log_len > 0) { + GLchar* log_buf = (GLchar*) _sg_malloc((size_t)log_len); + glGetProgramInfoLog(gl_prog, log_len, &log_len, log_buf); + _SG_ERROR(GL_SHADER_LINKING_FAILED); + _SG_LOGMSG(GL_SHADER_LINKING_FAILED, log_buf); + _sg_free(log_buf); + } + glDeleteProgram(gl_prog); + return SG_RESOURCESTATE_FAILED; + } + shd->gl.prog = gl_prog; + + // resolve uniforms + _SG_GL_CHECK_ERROR(); + for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) { + const sg_shader_uniform_block* ub_desc = &desc->uniform_blocks[ub_index]; + if (ub_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(ub_desc->size > 0); + _sg_gl_uniform_block_t* ub = &shd->gl.uniform_blocks[ub_index]; + SOKOL_ASSERT(ub->num_uniforms == 0); + uint32_t cur_uniform_offset = 0; + for (int u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) { + const sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { + break; + } + const uint32_t u_align = _sg_uniform_alignment(u_desc->type, u_desc->array_count, ub_desc->layout); + const uint32_t u_size = _sg_uniform_size(u_desc->type, u_desc->array_count, ub_desc->layout); + cur_uniform_offset = _sg_align_u32(cur_uniform_offset, u_align); + _sg_gl_uniform_t* u = &ub->uniforms[u_index]; + u->type = u_desc->type; + u->count = (uint16_t) u_desc->array_count; + u->offset = (uint16_t) cur_uniform_offset; + SOKOL_ASSERT(u_desc->glsl_name); + u->gl_loc = glGetUniformLocation(gl_prog, u_desc->glsl_name); + if (u->gl_loc == -1) { + _SG_WARN(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER, u_desc->glsl_name); + } + cur_uniform_offset += u_size; + ub->num_uniforms++; + } + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + cur_uniform_offset = _sg_align_u32(cur_uniform_offset, 16); + } + SOKOL_ASSERT(ub_desc->size == (size_t)cur_uniform_offset); + _SOKOL_UNUSED(cur_uniform_offset); + } + + // copy storage buffer bind slots + for (size_t sbuf_index = 0; sbuf_index < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_index++) { + const sg_shader_storage_buffer* sbuf_desc = &desc->storage_buffers[sbuf_index]; + if (sbuf_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(sbuf_desc->glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS); + shd->gl.sbuf_binding[sbuf_index] = sbuf_desc->glsl_binding_n; + } + + // record image sampler location in shader program + _SG_GL_CHECK_ERROR(); + GLuint cur_prog = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&cur_prog); + glUseProgram(gl_prog); + GLint gl_tex_slot = 0; + for (size_t img_smp_index = 0; img_smp_index < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_index++) { + const sg_shader_image_sampler_pair* img_smp_desc = &desc->image_sampler_pairs[img_smp_index]; + if (img_smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(img_smp_desc->glsl_name); + GLint gl_loc = glGetUniformLocation(gl_prog, img_smp_desc->glsl_name); + if (gl_loc != -1) { + glUniform1i(gl_loc, gl_tex_slot); + shd->gl.tex_slot[img_smp_index] = (int8_t)gl_tex_slot++; + } else { + shd->gl.tex_slot[img_smp_index] = -1; + _SG_WARN(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER, img_smp_desc->glsl_name); + } + } + + // it's legal to call glUseProgram with 0 + glUseProgram(cur_prog); + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _SG_GL_CHECK_ERROR(); + if (shd->gl.prog) { + _sg_gl_cache_invalidate_program(shd->gl.prog); + glDeleteProgram(shd->gl.prog); + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && shd && desc); + SOKOL_ASSERT((pip->shader == 0) && (pip->cmn.shader_id.id != SG_INVALID_ID)); + SOKOL_ASSERT(desc->shader.id == shd->slot.id); + SOKOL_ASSERT(shd->gl.prog); + SOKOL_ASSERT(_sg.limits.max_vertex_attrs <= SG_MAX_VERTEX_ATTRIBUTES); + pip->shader = shd; + if (pip->cmn.is_compute) { + // shortcut for compute pipelines + return SG_RESOURCESTATE_VALID; + } + pip->gl.primitive_type = desc->primitive_type; + pip->gl.depth = desc->depth; + pip->gl.stencil = desc->stencil; + // FIXME: blend color and write mask per draw-buffer-attachment (requires GL4) + pip->gl.blend = desc->colors[0].blend; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + pip->gl.color_write_mask[i] = desc->colors[i].write_mask; + } + pip->gl.cull_mode = desc->cull_mode; + pip->gl.face_winding = desc->face_winding; + pip->gl.sample_count = desc->sample_count; + pip->gl.alpha_to_coverage_enabled = desc->alpha_to_coverage_enabled; + + // NOTE: GLSL compilers may remove unused vertex attributes so we can't rely + // on the 'prepopulated' vertex_buffer_layout_active[] state and need to + // fill this array from scratch with the actual info after GLSL compilation + for (int i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + pip->cmn.vertex_buffer_layout_active[i] = false; + } + + // resolve vertex attributes + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + pip->gl.attrs[attr_index].vb_index = -1; + } + for (int attr_index = 0; attr_index < _sg.limits.max_vertex_attrs; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[a_state->buffer_index]; + const sg_vertex_step step_func = l_state->step_func; + const int step_rate = l_state->step_rate; + GLint attr_loc = attr_index; + if (!_sg_strempty(&shd->gl.attrs[attr_index].name)) { + attr_loc = glGetAttribLocation(pip->shader->gl.prog, _sg_strptr(&shd->gl.attrs[attr_index].name)); + } + if (attr_loc != -1) { + SOKOL_ASSERT(attr_loc < (GLint)_sg.limits.max_vertex_attrs); + _sg_gl_attr_t* gl_attr = &pip->gl.attrs[attr_loc]; + SOKOL_ASSERT(gl_attr->vb_index == -1); + gl_attr->vb_index = (int8_t) a_state->buffer_index; + if (step_func == SG_VERTEXSTEP_PER_VERTEX) { + gl_attr->divisor = 0; + } else { + gl_attr->divisor = (int8_t) step_rate; + pip->cmn.use_instanced_draw = true; + } + SOKOL_ASSERT(l_state->stride > 0); + gl_attr->stride = (uint8_t) l_state->stride; + gl_attr->offset = a_state->offset; + gl_attr->size = (uint8_t) _sg_gl_vertexformat_size(a_state->format); + gl_attr->type = _sg_gl_vertexformat_type(a_state->format); + gl_attr->normalized = _sg_gl_vertexformat_normalized(a_state->format); + gl_attr->base_type = _sg_vertexformat_basetype(a_state->format); + pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true; + } else { + _SG_WARN(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, _sg_strptr(&shd->gl.attrs[attr_index].name)); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_gl_cache_invalidate_pipeline(pip); +} + +_SOKOL_PRIVATE void _sg_gl_fb_attach_texture(const _sg_gl_attachment_t* gl_att, const _sg_attachment_common_t* cmn_att, GLenum gl_att_type) { + const _sg_image_t* img = gl_att->image; + SOKOL_ASSERT(img); + const GLuint gl_tex = img->gl.tex[0]; + SOKOL_ASSERT(gl_tex); + const GLuint gl_target = img->gl.target; + SOKOL_ASSERT(gl_target); + const int mip_level = cmn_att->mip_level; + const int slice = cmn_att->slice; + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, gl_target, gl_tex, mip_level); + break; + case SG_IMAGETYPE_CUBE: + glFramebufferTexture2D(GL_FRAMEBUFFER, gl_att_type, _sg_gl_cubeface_target(slice), gl_tex, mip_level); + break; + default: + glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_att_type, gl_tex, mip_level, slice); + break; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_depth_stencil_attachment_type(const _sg_gl_attachment_t* ds_att) { + const _sg_image_t* img = ds_att->image; + SOKOL_ASSERT(img); + if (_sg_is_depth_stencil_format(img->cmn.pixel_format)) { + return GL_DEPTH_STENCIL_ATTACHMENT; + } else { + return GL_DEPTH_ATTACHMENT; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_gl_create_attachments(_sg_attachments_t* atts, _sg_image_t** color_images, _sg_image_t** resolve_images, _sg_image_t* ds_image, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + SOKOL_ASSERT(color_images && resolve_images); + _SG_GL_CHECK_ERROR(); + + // copy image pointers + for (int i = 0; i < atts->cmn.num_colors; i++) { + const sg_attachment_desc* color_desc = &desc->colors[i]; + _SOKOL_UNUSED(color_desc); + SOKOL_ASSERT(color_desc->image.id != SG_INVALID_ID); + SOKOL_ASSERT(0 == atts->gl.colors[i].image); + SOKOL_ASSERT(color_images[i] && (color_images[i]->slot.id == color_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(color_images[i]->cmn.pixel_format)); + atts->gl.colors[i].image = color_images[i]; + + const sg_attachment_desc* resolve_desc = &desc->resolves[i]; + if (resolve_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(0 == atts->gl.resolves[i].image); + SOKOL_ASSERT(resolve_images[i] && (resolve_images[i]->slot.id == resolve_desc->image.id)); + SOKOL_ASSERT(color_images[i] && (color_images[i]->cmn.pixel_format == resolve_images[i]->cmn.pixel_format)); + atts->gl.resolves[i].image = resolve_images[i]; + } + } + SOKOL_ASSERT(0 == atts->gl.depth_stencil.image); + const sg_attachment_desc* ds_desc = &desc->depth_stencil; + if (ds_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(ds_image && (ds_image->slot.id == ds_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(ds_image->cmn.pixel_format)); + atts->gl.depth_stencil.image = ds_image; + } + + // store current framebuffer binding (restored at end of function) + GLuint gl_orig_fb; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&gl_orig_fb); + + // create a framebuffer object + glGenFramebuffers(1, &atts->gl.fb); + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.fb); + + // attach color attachments to framebuffer + for (int i = 0; i < atts->cmn.num_colors; i++) { + const _sg_image_t* color_img = atts->gl.colors[i].image; + SOKOL_ASSERT(color_img); + const GLuint gl_msaa_render_buffer = color_img->gl.msaa_render_buffer; + if (gl_msaa_render_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, (GLenum)(GL_COLOR_ATTACHMENT0+i), GL_RENDERBUFFER, gl_msaa_render_buffer); + } else { + const GLenum gl_att_type = (GLenum)(GL_COLOR_ATTACHMENT0 + i); + _sg_gl_fb_attach_texture(&atts->gl.colors[i], &atts->cmn.colors[i], gl_att_type); + } + } + // attach depth-stencil attachment + if (atts->gl.depth_stencil.image) { + const GLenum gl_att = _sg_gl_depth_stencil_attachment_type(&atts->gl.depth_stencil); + const _sg_image_t* ds_img = atts->gl.depth_stencil.image; + const GLuint gl_msaa_render_buffer = ds_img->gl.msaa_render_buffer; + if (gl_msaa_render_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, gl_att, GL_RENDERBUFFER, gl_msaa_render_buffer); + } else { + const GLenum gl_att_type = _sg_gl_depth_stencil_attachment_type(&atts->gl.depth_stencil); + _sg_gl_fb_attach_texture(&atts->gl.depth_stencil, &atts->cmn.depth_stencil, gl_att_type); + } + } + + // check if framebuffer is complete + { + const GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + switch (fb_status) { + case GL_FRAMEBUFFER_UNDEFINED: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNDEFINED); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT); + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNSUPPORTED); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE); + break; + default: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNKNOWN); + break; + } + return SG_RESOURCESTATE_FAILED; + } + } + + // setup color attachments for the framebuffer + static const GLenum gl_draw_bufs[SG_MAX_COLOR_ATTACHMENTS] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3 + }; + glDrawBuffers(atts->cmn.num_colors, gl_draw_bufs); + + // create MSAA resolve framebuffers if necessary + for (int i = 0; i < atts->cmn.num_colors; i++) { + _sg_gl_attachment_t* gl_resolve_att = &atts->gl.resolves[i]; + if (gl_resolve_att->image) { + _sg_attachment_common_t* cmn_resolve_att = &atts->cmn.resolves[i]; + SOKOL_ASSERT(0 == atts->gl.msaa_resolve_framebuffer[i]); + glGenFramebuffers(1, &atts->gl.msaa_resolve_framebuffer[i]); + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.msaa_resolve_framebuffer[i]); + _sg_gl_fb_attach_texture(gl_resolve_att, cmn_resolve_att, GL_COLOR_ATTACHMENT0); + // check if framebuffer is complete + const GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + switch (fb_status) { + case GL_FRAMEBUFFER_UNDEFINED: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNDEFINED); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT); + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNSUPPORTED); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE); + break; + default: + _SG_ERROR(GL_FRAMEBUFFER_STATUS_UNKNOWN); + break; + } + return SG_RESOURCESTATE_FAILED; + } + // setup color attachments for the framebuffer + glDrawBuffers(1, &gl_draw_bufs[0]); + } + } + + // restore original framebuffer binding + glBindFramebuffer(GL_FRAMEBUFFER, gl_orig_fb); + _SG_GL_CHECK_ERROR(); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_gl_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _SG_GL_CHECK_ERROR(); + if (0 != atts->gl.fb) { + glDeleteFramebuffers(1, &atts->gl.fb); + } + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts->gl.msaa_resolve_framebuffer[i]) { + glDeleteFramebuffers(1, &atts->gl.msaa_resolve_framebuffer[i]); + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE _sg_image_t* _sg_gl_attachments_color_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->gl.colors[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_gl_attachments_resolve_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->gl.resolves[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_gl_attachments_ds_image(const _sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + return atts->gl.depth_stencil.image; +} + +_SOKOL_PRIVATE void _sg_gl_begin_pass(const sg_pass* pass) { + // FIXME: what if a texture used as render target is still bound, should we + // unbind all currently bound textures in begin pass? + SOKOL_ASSERT(pass); + _SG_GL_CHECK_ERROR(); + + // early out if this a compute pass + if (pass->compute) { + return; + } + + const _sg_attachments_t* atts = _sg.cur_pass.atts; + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + // bind the render pass framebuffer + // + // FIXME: Disabling SRGB conversion for the default framebuffer is + // a crude hack to make behaviour for sRGB render target textures + // identical with the Metal and D3D11 swapchains created by sokol-app. + // + // This will need a cleaner solution (e.g. allowing to configure + // sokol_app.h with an sRGB or RGB framebuffer. + if (atts) { + // offscreen pass + SOKOL_ASSERT(atts->gl.fb); + #if defined(SOKOL_GLCORE) + glEnable(GL_FRAMEBUFFER_SRGB); + #endif + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.fb); + } else { + // default pass + #if defined(SOKOL_GLCORE) + glDisable(GL_FRAMEBUFFER_SRGB); + #endif + // NOTE: on some platforms, the default framebuffer of a context + // is null, so we can't actually assert here that the + // framebuffer has been provided + glBindFramebuffer(GL_FRAMEBUFFER, swapchain->gl.framebuffer); + } + glViewport(0, 0, _sg.cur_pass.width, _sg.cur_pass.height); + glScissor(0, 0, _sg.cur_pass.width, _sg.cur_pass.height); + + // number of color attachments + const int num_color_atts = atts ? atts->cmn.num_colors : 1; + + // clear color and depth-stencil attachments if needed + bool clear_any_color = false; + for (int i = 0; i < num_color_atts; i++) { + if (SG_LOADACTION_CLEAR == action->colors[i].load_action) { + clear_any_color = true; + break; + } + } + const bool clear_depth = (action->depth.load_action == SG_LOADACTION_CLEAR); + const bool clear_stencil = (action->stencil.load_action == SG_LOADACTION_CLEAR); + + bool need_pip_cache_flush = false; + if (clear_any_color) { + bool need_color_mask_flush = false; + // NOTE: not a bug to iterate over all possible color attachments + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (SG_COLORMASK_RGBA != _sg.gl.cache.color_write_mask[i]) { + need_pip_cache_flush = true; + need_color_mask_flush = true; + _sg.gl.cache.color_write_mask[i] = SG_COLORMASK_RGBA; + } + } + if (need_color_mask_flush) { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + } + if (clear_depth) { + if (!_sg.gl.cache.depth.write_enabled) { + need_pip_cache_flush = true; + _sg.gl.cache.depth.write_enabled = true; + glDepthMask(GL_TRUE); + } + if (_sg.gl.cache.depth.compare != SG_COMPAREFUNC_ALWAYS) { + need_pip_cache_flush = true; + _sg.gl.cache.depth.compare = SG_COMPAREFUNC_ALWAYS; + glDepthFunc(GL_ALWAYS); + } + } + if (clear_stencil) { + if (_sg.gl.cache.stencil.write_mask != 0xFF) { + need_pip_cache_flush = true; + _sg.gl.cache.stencil.write_mask = 0xFF; + glStencilMask(0xFF); + } + } + if (need_pip_cache_flush) { + // we messed with the state cache directly, need to clear cached + // pipeline to force re-evaluation in next sg_apply_pipeline() + _sg.gl.cache.cur_pipeline = 0; + _sg.gl.cache.cur_pipeline_id.id = SG_INVALID_ID; + } + for (int i = 0; i < num_color_atts; i++) { + if (action->colors[i].load_action == SG_LOADACTION_CLEAR) { + glClearBufferfv(GL_COLOR, i, &action->colors[i].clear_value.r); + } + } + if ((atts == 0) || (atts->gl.depth_stencil.image)) { + if (clear_depth && clear_stencil) { + glClearBufferfi(GL_DEPTH_STENCIL, 0, action->depth.clear_value, action->stencil.clear_value); + } else if (clear_depth) { + glClearBufferfv(GL_DEPTH, 0, &action->depth.clear_value); + } else if (clear_stencil) { + GLint val = (GLint) action->stencil.clear_value; + glClearBufferiv(GL_STENCIL, 0, &val); + } + } + // keep store actions for end-pass + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + _sg.gl.color_store_actions[i] = action->colors[i].store_action; + } + _sg.gl.depth_store_action = action->depth.store_action; + _sg.gl.stencil_store_action = action->stencil.store_action; + + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_end_pass(void) { + _SG_GL_CHECK_ERROR(); + + if (_sg.cur_pass.atts) { + const _sg_attachments_t* atts = _sg.cur_pass.atts; + SOKOL_ASSERT(atts->slot.id == _sg.cur_pass.atts_id.id); + bool fb_read_bound = false; + bool fb_draw_bound = false; + const int num_color_atts = atts->cmn.num_colors; + for (int i = 0; i < num_color_atts; i++) { + // perform MSAA resolve if needed + if (atts->gl.msaa_resolve_framebuffer[i] != 0) { + if (!fb_read_bound) { + SOKOL_ASSERT(atts->gl.fb); + glBindFramebuffer(GL_READ_FRAMEBUFFER, atts->gl.fb); + fb_read_bound = true; + } + const int w = atts->gl.colors[i].image->cmn.width; + const int h = atts->gl.colors[i].image->cmn.height; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, atts->gl.msaa_resolve_framebuffer[i]); + glReadBuffer((GLenum)(GL_COLOR_ATTACHMENT0 + i)); + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + fb_draw_bound = true; + } + } + + // invalidate framebuffers + _SOKOL_UNUSED(fb_draw_bound); + #if defined(SOKOL_GLES3) + // need to restore framebuffer binding before invalidate if the MSAA resolve had changed the binding + if (fb_draw_bound) { + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.fb); + } + GLenum invalidate_atts[SG_MAX_COLOR_ATTACHMENTS + 2] = { 0 }; + int att_index = 0; + for (int i = 0; i < num_color_atts; i++) { + if (_sg.gl.color_store_actions[i] == SG_STOREACTION_DONTCARE) { + invalidate_atts[att_index++] = (GLenum)(GL_COLOR_ATTACHMENT0 + i); + } + } + if ((_sg.gl.depth_store_action == SG_STOREACTION_DONTCARE) && (_sg.cur_pass.atts->cmn.depth_stencil.image_id.id != SG_INVALID_ID)) { + invalidate_atts[att_index++] = GL_DEPTH_ATTACHMENT; + } + if ((_sg.gl.stencil_store_action == SG_STOREACTION_DONTCARE) && (_sg.cur_pass.atts->cmn.depth_stencil.image_id.id != SG_INVALID_ID)) { + invalidate_atts[att_index++] = GL_STENCIL_ATTACHMENT; + } + if (att_index > 0) { + glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, att_index, invalidate_atts); + } + #endif + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + y = origin_top_left ? (_sg.cur_pass.height - (y+h)) : y; + glViewport(x, y, w, h); +} + +_SOKOL_PRIVATE void _sg_gl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + y = origin_top_left ? (_sg.cur_pass.height - (y+h)) : y; + glScissor(x, y, w, h); +} + +_SOKOL_PRIVATE void _sg_gl_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + _SG_GL_CHECK_ERROR(); + if ((_sg.gl.cache.cur_pipeline != pip) || (_sg.gl.cache.cur_pipeline_id.id != pip->slot.id)) { + _sg.gl.cache.cur_pipeline = pip; + _sg.gl.cache.cur_pipeline_id.id = pip->slot.id; + + // bind shader program + if (pip->shader->gl.prog != _sg.gl.cache.prog) { + _sg.gl.cache.prog = pip->shader->gl.prog; + glUseProgram(pip->shader->gl.prog); + _sg_stats_add(gl.num_use_program, 1); + } + + // if this is a compute pass, can early-out here + if (pip->cmn.is_compute) { + _SG_GL_CHECK_ERROR(); + return; + } + + // update render pipeline state + _sg.gl.cache.cur_primitive_type = _sg_gl_primitive_type(pip->gl.primitive_type); + _sg.gl.cache.cur_index_type = _sg_gl_index_type(pip->cmn.index_type); + + // update depth state + { + const sg_depth_state* state_ds = &pip->gl.depth; + sg_depth_state* cache_ds = &_sg.gl.cache.depth; + if (state_ds->compare != cache_ds->compare) { + cache_ds->compare = state_ds->compare; + glDepthFunc(_sg_gl_compare_func(state_ds->compare)); + _sg_stats_add(gl.num_render_state, 1); + } + if (state_ds->write_enabled != cache_ds->write_enabled) { + cache_ds->write_enabled = state_ds->write_enabled; + glDepthMask(state_ds->write_enabled); + _sg_stats_add(gl.num_render_state, 1); + } + if (!_sg_fequal(state_ds->bias, cache_ds->bias, 0.000001f) || + !_sg_fequal(state_ds->bias_slope_scale, cache_ds->bias_slope_scale, 0.000001f)) + { + /* according to ANGLE's D3D11 backend: + D3D11 SlopeScaledDepthBias ==> GL polygonOffsetFactor + D3D11 DepthBias ==> GL polygonOffsetUnits + DepthBiasClamp has no meaning on GL + */ + cache_ds->bias = state_ds->bias; + cache_ds->bias_slope_scale = state_ds->bias_slope_scale; + glPolygonOffset(state_ds->bias_slope_scale, state_ds->bias); + _sg_stats_add(gl.num_render_state, 1); + bool po_enabled = true; + if (_sg_fequal(state_ds->bias, 0.0f, 0.000001f) && + _sg_fequal(state_ds->bias_slope_scale, 0.0f, 0.000001f)) + { + po_enabled = false; + } + if (po_enabled != _sg.gl.cache.polygon_offset_enabled) { + _sg.gl.cache.polygon_offset_enabled = po_enabled; + if (po_enabled) { + glEnable(GL_POLYGON_OFFSET_FILL); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } + _sg_stats_add(gl.num_render_state, 1); + } + } + } + + // update stencil state + { + const sg_stencil_state* state_ss = &pip->gl.stencil; + sg_stencil_state* cache_ss = &_sg.gl.cache.stencil; + if (state_ss->enabled != cache_ss->enabled) { + cache_ss->enabled = state_ss->enabled; + if (state_ss->enabled) { + glEnable(GL_STENCIL_TEST); + } else { + glDisable(GL_STENCIL_TEST); + } + _sg_stats_add(gl.num_render_state, 1); + } + if (state_ss->write_mask != cache_ss->write_mask) { + cache_ss->write_mask = state_ss->write_mask; + glStencilMask(state_ss->write_mask); + _sg_stats_add(gl.num_render_state, 1); + } + for (int i = 0; i < 2; i++) { + const sg_stencil_face_state* state_sfs = (i==0)? &state_ss->front : &state_ss->back; + sg_stencil_face_state* cache_sfs = (i==0)? &cache_ss->front : &cache_ss->back; + GLenum gl_face = (i==0)? GL_FRONT : GL_BACK; + if ((state_sfs->compare != cache_sfs->compare) || + (state_ss->read_mask != cache_ss->read_mask) || + (state_ss->ref != cache_ss->ref)) + { + cache_sfs->compare = state_sfs->compare; + glStencilFuncSeparate(gl_face, + _sg_gl_compare_func(state_sfs->compare), + state_ss->ref, + state_ss->read_mask); + _sg_stats_add(gl.num_render_state, 1); + } + if ((state_sfs->fail_op != cache_sfs->fail_op) || + (state_sfs->depth_fail_op != cache_sfs->depth_fail_op) || + (state_sfs->pass_op != cache_sfs->pass_op)) + { + cache_sfs->fail_op = state_sfs->fail_op; + cache_sfs->depth_fail_op = state_sfs->depth_fail_op; + cache_sfs->pass_op = state_sfs->pass_op; + glStencilOpSeparate(gl_face, + _sg_gl_stencil_op(state_sfs->fail_op), + _sg_gl_stencil_op(state_sfs->depth_fail_op), + _sg_gl_stencil_op(state_sfs->pass_op)); + _sg_stats_add(gl.num_render_state, 1); + } + } + cache_ss->read_mask = state_ss->read_mask; + cache_ss->ref = state_ss->ref; + } + + if (pip->cmn.color_count > 0) { + // update blend state + // FIXME: separate blend state per color attachment + const sg_blend_state* state_bs = &pip->gl.blend; + sg_blend_state* cache_bs = &_sg.gl.cache.blend; + if (state_bs->enabled != cache_bs->enabled) { + cache_bs->enabled = state_bs->enabled; + if (state_bs->enabled) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + _sg_stats_add(gl.num_render_state, 1); + } + if ((state_bs->src_factor_rgb != cache_bs->src_factor_rgb) || + (state_bs->dst_factor_rgb != cache_bs->dst_factor_rgb) || + (state_bs->src_factor_alpha != cache_bs->src_factor_alpha) || + (state_bs->dst_factor_alpha != cache_bs->dst_factor_alpha)) + { + cache_bs->src_factor_rgb = state_bs->src_factor_rgb; + cache_bs->dst_factor_rgb = state_bs->dst_factor_rgb; + cache_bs->src_factor_alpha = state_bs->src_factor_alpha; + cache_bs->dst_factor_alpha = state_bs->dst_factor_alpha; + glBlendFuncSeparate(_sg_gl_blend_factor(state_bs->src_factor_rgb), + _sg_gl_blend_factor(state_bs->dst_factor_rgb), + _sg_gl_blend_factor(state_bs->src_factor_alpha), + _sg_gl_blend_factor(state_bs->dst_factor_alpha)); + _sg_stats_add(gl.num_render_state, 1); + } + if ((state_bs->op_rgb != cache_bs->op_rgb) || (state_bs->op_alpha != cache_bs->op_alpha)) { + cache_bs->op_rgb = state_bs->op_rgb; + cache_bs->op_alpha = state_bs->op_alpha; + glBlendEquationSeparate(_sg_gl_blend_op(state_bs->op_rgb), _sg_gl_blend_op(state_bs->op_alpha)); + _sg_stats_add(gl.num_render_state, 1); + } + + // standalone color target state + for (GLuint i = 0; i < (GLuint)pip->cmn.color_count; i++) { + if (pip->gl.color_write_mask[i] != _sg.gl.cache.color_write_mask[i]) { + const sg_color_mask cm = pip->gl.color_write_mask[i]; + _sg.gl.cache.color_write_mask[i] = cm; + #ifdef SOKOL_GLCORE + glColorMaski(i, + (cm & SG_COLORMASK_R) != 0, + (cm & SG_COLORMASK_G) != 0, + (cm & SG_COLORMASK_B) != 0, + (cm & SG_COLORMASK_A) != 0); + #else + if (0 == i) { + glColorMask((cm & SG_COLORMASK_R) != 0, + (cm & SG_COLORMASK_G) != 0, + (cm & SG_COLORMASK_B) != 0, + (cm & SG_COLORMASK_A) != 0); + } + #endif + _sg_stats_add(gl.num_render_state, 1); + } + } + + if (!_sg_fequal(pip->cmn.blend_color.r, _sg.gl.cache.blend_color.r, 0.0001f) || + !_sg_fequal(pip->cmn.blend_color.g, _sg.gl.cache.blend_color.g, 0.0001f) || + !_sg_fequal(pip->cmn.blend_color.b, _sg.gl.cache.blend_color.b, 0.0001f) || + !_sg_fequal(pip->cmn.blend_color.a, _sg.gl.cache.blend_color.a, 0.0001f)) + { + sg_color c = pip->cmn.blend_color; + _sg.gl.cache.blend_color = c; + glBlendColor(c.r, c.g, c.b, c.a); + _sg_stats_add(gl.num_render_state, 1); + } + } // pip->cmn.color_count > 0 + + if (pip->gl.cull_mode != _sg.gl.cache.cull_mode) { + _sg.gl.cache.cull_mode = pip->gl.cull_mode; + if (SG_CULLMODE_NONE == pip->gl.cull_mode) { + glDisable(GL_CULL_FACE); + _sg_stats_add(gl.num_render_state, 1); + } else { + glEnable(GL_CULL_FACE); + GLenum gl_mode = (SG_CULLMODE_FRONT == pip->gl.cull_mode) ? GL_FRONT : GL_BACK; + glCullFace(gl_mode); + _sg_stats_add(gl.num_render_state, 2); + } + } + if (pip->gl.face_winding != _sg.gl.cache.face_winding) { + _sg.gl.cache.face_winding = pip->gl.face_winding; + GLenum gl_winding = (SG_FACEWINDING_CW == pip->gl.face_winding) ? GL_CW : GL_CCW; + glFrontFace(gl_winding); + _sg_stats_add(gl.num_render_state, 1); + } + if (pip->gl.alpha_to_coverage_enabled != _sg.gl.cache.alpha_to_coverage_enabled) { + _sg.gl.cache.alpha_to_coverage_enabled = pip->gl.alpha_to_coverage_enabled; + if (pip->gl.alpha_to_coverage_enabled) { + glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); + } + _sg_stats_add(gl.num_render_state, 1); + } + #ifdef SOKOL_GLCORE + if (pip->gl.sample_count != _sg.gl.cache.sample_count) { + _sg.gl.cache.sample_count = pip->gl.sample_count; + if (pip->gl.sample_count > 1) { + glEnable(GL_MULTISAMPLE); + } else { + glDisable(GL_MULTISAMPLE); + } + _sg_stats_add(gl.num_render_state, 1); + } + #endif + + } + _SG_GL_CHECK_ERROR(); +} + +#if defined _SOKOL_GL_HAS_COMPUTE +_SOKOL_PRIVATE void _sg_gl_handle_memory_barriers(const _sg_shader_t* shd, const _sg_bindings_t* bnd) { + if (!_sg.features.compute) { + return; + } + // NOTE: currently only storage buffers can be GPU-written, and storage + // buffers cannot be bound as vertex- or index-buffers. + bool needs_barrier = false; + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + _sg_buffer_t* buf = bnd->sbufs[i]; + // if this buffer has pending GPU changes, issue a memory barrier + if (buf->gl.gpu_dirty) { + buf->gl.gpu_dirty = false; + needs_barrier = true; + } + // if this binding is going to be written by the GPU set the buffer to 'gpu_dirty' + if (!shd->cmn.storage_buffers[i].readonly) { + buf->gl.gpu_dirty = true; + } + } + if (needs_barrier) { + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + _sg_stats_add(gl.num_memory_barriers, 1); + } +} +#endif + +_SOKOL_PRIVATE bool _sg_gl_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip && bnd->pip->shader); + SOKOL_ASSERT(bnd->pip->shader->slot.id == bnd->pip->cmn.shader_id.id); + _SG_GL_CHECK_ERROR(); + const _sg_shader_t* shd = bnd->pip->shader; + + // take care of storage buffer memory barriers + #if defined(_SOKOL_GL_HAS_COMPUTE) + _sg_gl_handle_memory_barriers(shd, bnd); + #endif + + // bind combined image-samplers + _SG_GL_CHECK_ERROR(); + for (size_t img_smp_index = 0; img_smp_index < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_index++) { + const _sg_shader_image_sampler_t* img_smp = &shd->cmn.image_samplers[img_smp_index]; + if (img_smp->stage == SG_SHADERSTAGE_NONE) { + continue; + } + const int8_t gl_tex_slot = (GLint)shd->gl.tex_slot[img_smp_index]; + if (gl_tex_slot != -1) { + SOKOL_ASSERT(img_smp->image_slot < SG_MAX_IMAGE_BINDSLOTS); + SOKOL_ASSERT(img_smp->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS); + const _sg_image_t* img = bnd->imgs[img_smp->image_slot]; + const _sg_sampler_t* smp = bnd->smps[img_smp->sampler_slot]; + SOKOL_ASSERT(img); + SOKOL_ASSERT(smp); + const GLenum gl_tgt = img->gl.target; + const GLuint gl_tex = img->gl.tex[img->cmn.active_slot]; + const GLuint gl_smp = smp->gl.smp; + _sg_gl_cache_bind_texture_sampler(gl_tex_slot, gl_tgt, gl_tex, gl_smp); + } + } + _SG_GL_CHECK_ERROR(); + + // bind storage buffers + for (size_t sbuf_index = 0; sbuf_index < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_index++) { + if (shd->cmn.storage_buffers[sbuf_index].stage == SG_SHADERSTAGE_NONE) { + continue; + } + const _sg_buffer_t* sbuf = bnd->sbufs[sbuf_index]; + const uint8_t binding = shd->gl.sbuf_binding[sbuf_index]; + GLuint gl_sbuf = sbuf->gl.buf[sbuf->cmn.active_slot]; + _sg_gl_cache_bind_storage_buffer(binding, gl_sbuf); + } + _SG_GL_CHECK_ERROR(); + + // if compute-pipeline, early out here + if (bnd->pip->cmn.is_compute) { + return true; + } + + // index buffer (can be 0) + const GLuint gl_ib = bnd->ib ? bnd->ib->gl.buf[bnd->ib->cmn.active_slot] : 0; + _sg_gl_cache_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, gl_ib); + _sg.gl.cache.cur_ib_offset = bnd->ib_offset; + + // vertex attributes + for (GLuint attr_index = 0; attr_index < (GLuint)_sg.limits.max_vertex_attrs; attr_index++) { + _sg_gl_attr_t* attr = &bnd->pip->gl.attrs[attr_index]; + _sg_gl_cache_attr_t* cache_attr = &_sg.gl.cache.attrs[attr_index]; + bool cache_attr_dirty = false; + int vb_offset = 0; + GLuint gl_vb = 0; + if (attr->vb_index >= 0) { + // attribute is enabled + SOKOL_ASSERT(attr->vb_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + _sg_buffer_t* vb = bnd->vbs[attr->vb_index]; + SOKOL_ASSERT(vb); + gl_vb = vb->gl.buf[vb->cmn.active_slot]; + vb_offset = bnd->vb_offsets[attr->vb_index] + attr->offset; + if ((gl_vb != cache_attr->gl_vbuf) || + (attr->size != cache_attr->gl_attr.size) || + (attr->type != cache_attr->gl_attr.type) || + (attr->normalized != cache_attr->gl_attr.normalized) || + (attr->base_type != cache_attr->gl_attr.base_type) || + (attr->stride != cache_attr->gl_attr.stride) || + (vb_offset != cache_attr->gl_attr.offset) || + (cache_attr->gl_attr.divisor != attr->divisor)) + { + _sg_gl_cache_bind_buffer(GL_ARRAY_BUFFER, gl_vb); + if (attr->base_type == SG_SHADERATTRBASETYPE_FLOAT) { + glVertexAttribPointer(attr_index, attr->size, attr->type, attr->normalized, attr->stride, (const GLvoid*)(GLintptr)vb_offset); + } else { + glVertexAttribIPointer(attr_index, attr->size, attr->type, attr->stride, (const GLvoid*)(GLintptr)vb_offset); + } + _sg_stats_add(gl.num_vertex_attrib_pointer, 1); + glVertexAttribDivisor(attr_index, (GLuint)attr->divisor); + _sg_stats_add(gl.num_vertex_attrib_divisor, 1); + cache_attr_dirty = true; + } + if (cache_attr->gl_attr.vb_index == -1) { + glEnableVertexAttribArray(attr_index); + _sg_stats_add(gl.num_enable_vertex_attrib_array, 1); + cache_attr_dirty = true; + } + } else { + // attribute is disabled + if (cache_attr->gl_attr.vb_index != -1) { + glDisableVertexAttribArray(attr_index); + _sg_stats_add(gl.num_disable_vertex_attrib_array, 1); + cache_attr_dirty = true; + } + } + if (cache_attr_dirty) { + cache_attr->gl_attr = *attr; + cache_attr->gl_attr.offset = vb_offset; + cache_attr->gl_vbuf = gl_vb; + } + } + _SG_GL_CHECK_ERROR(); + return true; +} + +_SOKOL_PRIVATE void _sg_gl_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + const _sg_pipeline_t* pip = _sg.gl.cache.cur_pipeline; + SOKOL_ASSERT(pip && pip->shader); + SOKOL_ASSERT(pip->slot.id == _sg.gl.cache.cur_pipeline_id.id); + const _sg_shader_t* shd = pip->shader; + SOKOL_ASSERT(shd->slot.id == pip->cmn.shader_id.id); + SOKOL_ASSERT(SG_SHADERSTAGE_NONE != shd->cmn.uniform_blocks[ub_slot].stage); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + const _sg_gl_uniform_block_t* gl_ub = &shd->gl.uniform_blocks[ub_slot]; + for (int u_index = 0; u_index < gl_ub->num_uniforms; u_index++) { + const _sg_gl_uniform_t* u = &gl_ub->uniforms[u_index]; + SOKOL_ASSERT(u->type != SG_UNIFORMTYPE_INVALID); + if (u->gl_loc == -1) { + continue; + } + _sg_stats_add(gl.num_uniform, 1); + GLfloat* fptr = (GLfloat*) (((uint8_t*)data->ptr) + u->offset); + GLint* iptr = (GLint*) (((uint8_t*)data->ptr) + u->offset); + switch (u->type) { + case SG_UNIFORMTYPE_INVALID: + break; + case SG_UNIFORMTYPE_FLOAT: + glUniform1fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT2: + glUniform2fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT3: + glUniform3fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_FLOAT4: + glUniform4fv(u->gl_loc, u->count, fptr); + break; + case SG_UNIFORMTYPE_INT: + glUniform1iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT2: + glUniform2iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT3: + glUniform3iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_INT4: + glUniform4iv(u->gl_loc, u->count, iptr); + break; + case SG_UNIFORMTYPE_MAT4: + glUniformMatrix4fv(u->gl_loc, u->count, GL_FALSE, fptr); + break; + default: + SOKOL_UNREACHABLE; + break; + } + } +} + +_SOKOL_PRIVATE void _sg_gl_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline); + const GLenum i_type = _sg.gl.cache.cur_index_type; + const GLenum p_type = _sg.gl.cache.cur_primitive_type; + const bool use_instanced_draw = (num_instances > 1) || (_sg.gl.cache.cur_pipeline->cmn.use_instanced_draw); + if (0 != i_type) { + // indexed rendering + const int i_size = (i_type == GL_UNSIGNED_SHORT) ? 2 : 4; + const int ib_offset = _sg.gl.cache.cur_ib_offset; + const GLvoid* indices = (const GLvoid*)(GLintptr)(base_element*i_size+ib_offset); + if (use_instanced_draw) { + glDrawElementsInstanced(p_type, num_elements, i_type, indices, num_instances); + } else { + glDrawElements(p_type, num_elements, i_type, indices); + } + } else { + // non-indexed rendering + if (use_instanced_draw) { + glDrawArraysInstanced(p_type, base_element, num_elements, num_instances); + } else { + glDrawArrays(p_type, base_element, num_elements); + } + } +} + +_SOKOL_PRIVATE void _sg_gl_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + #if defined(_SOKOL_GL_HAS_COMPUTE) + if (!_sg.features.compute) { + return; + } + glDispatchCompute((GLuint)num_groups_x, (GLuint)num_groups_y, (GLuint)num_groups_z); + #else + (void)num_groups_x; (void)num_groups_y; (void)num_groups_z; + #endif +} + +_SOKOL_PRIVATE void _sg_gl_commit(void) { + // "soft" clear bindings (only those that are actually bound) + _sg_gl_cache_clear_buffer_bindings(false); + _sg_gl_cache_clear_texture_sampler_bindings(false); +} + +_SOKOL_PRIVATE void _sg_gl_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + // only one update per buffer per frame allowed + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + GLenum gl_tgt = _sg_gl_buffer_target(buf->cmn.type); + SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); + GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot]; + SOKOL_ASSERT(gl_buf); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_store_buffer_binding(gl_tgt); + _sg_gl_cache_bind_buffer(gl_tgt, gl_buf); + glBufferSubData(gl_tgt, 0, (GLsizeiptr)data->size, data->ptr); + _sg_gl_cache_restore_buffer_binding(gl_tgt); + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + if (new_frame) { + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + } + GLenum gl_tgt = _sg_gl_buffer_target(buf->cmn.type); + SOKOL_ASSERT(buf->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); + GLuint gl_buf = buf->gl.buf[buf->cmn.active_slot]; + SOKOL_ASSERT(gl_buf); + _SG_GL_CHECK_ERROR(); + _sg_gl_cache_store_buffer_binding(gl_tgt); + _sg_gl_cache_bind_buffer(gl_tgt, gl_buf); + glBufferSubData(gl_tgt, buf->cmn.append_pos, (GLsizeiptr)data->size, data->ptr); + _sg_gl_cache_restore_buffer_binding(gl_tgt); + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE void _sg_gl_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + // only one update per image per frame allowed + if (++img->cmn.active_slot >= img->cmn.num_slots) { + img->cmn.active_slot = 0; + } + SOKOL_ASSERT(img->cmn.active_slot < SG_NUM_INFLIGHT_FRAMES); + SOKOL_ASSERT(0 != img->gl.tex[img->cmn.active_slot]); + _sg_gl_cache_store_texture_sampler_binding(0); + _sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[img->cmn.active_slot], 0); + const GLenum gl_img_format = _sg_gl_teximage_format(img->cmn.pixel_format); + const GLenum gl_img_type = _sg_gl_teximage_type(img->cmn.pixel_format); + const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1; + const int num_mips = img->cmn.num_mipmaps; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + GLenum gl_img_target = img->gl.target; + if (SG_IMAGETYPE_CUBE == img->cmn.type) { + gl_img_target = _sg_gl_cubeface_target(face_index); + } + const GLvoid* data_ptr = data->subimage[face_index][mip_index].ptr; + int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + if ((SG_IMAGETYPE_2D == img->cmn.type) || (SG_IMAGETYPE_CUBE == img->cmn.type)) { + glTexSubImage2D(gl_img_target, mip_index, + 0, 0, + mip_width, mip_height, + gl_img_format, gl_img_type, + data_ptr); + } else if ((SG_IMAGETYPE_3D == img->cmn.type) || (SG_IMAGETYPE_ARRAY == img->cmn.type)) { + int mip_depth = img->cmn.num_slices; + if (SG_IMAGETYPE_3D == img->cmn.type) { + mip_depth = _sg_miplevel_dim(img->cmn.num_slices, mip_index); + } + glTexSubImage3D(gl_img_target, mip_index, + 0, 0, 0, + mip_width, mip_height, mip_depth, + gl_img_format, gl_img_type, + data_ptr); + + } + } + } + _sg_gl_cache_restore_texture_sampler_binding(0); +} + +// ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ███ ███ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ █████ ██ ██ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██████ ██ ██ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>d3d11 backend +#elif defined(SOKOL_D3D11) + +#if defined(__cplusplus) +#define _sg_d3d11_AddRef(self) (self)->AddRef() +#else +#define _sg_d3d11_AddRef(self) (self)->lpVtbl->AddRef(self) +#endif + +#if defined(__cplusplus) +#define _sg_d3d11_Release(self) (self)->Release() +#else +#define _sg_d3d11_Release(self) (self)->lpVtbl->Release(self) +#endif + +// NOTE: This needs to be a macro since we can't use the polymorphism in C. It's called on many kinds of resources. +// NOTE: Based on microsoft docs, it's fine to call this with pData=NULL if DataSize is also zero. +#if defined(__cplusplus) +#define _sg_d3d11_SetPrivateData(self, guid, DataSize, pData) (self)->SetPrivateData(guid, DataSize, pData) +#else +#define _sg_d3d11_SetPrivateData(self, guid, DataSize, pData) (self)->lpVtbl->SetPrivateData(self, guid, DataSize, pData) +#endif + +#if defined(__cplusplus) +#define _sg_win32_refguid(guid) guid +#else +#define _sg_win32_refguid(guid) &guid +#endif + +static const GUID _sg_d3d11_WKPDID_D3DDebugObjectName = { 0x429b8c22,0x9188,0x4b0c, {0x87,0x42,0xac,0xb0,0xbf,0x85,0xc2,0x00} }; + +#if defined(SOKOL_DEBUG) +#define _sg_d3d11_setlabel(self, label) _sg_d3d11_SetPrivateData(self, _sg_win32_refguid(_sg_d3d11_WKPDID_D3DDebugObjectName), label ? (UINT)strlen(label) : 0, label) +#else +#define _sg_d3d11_setlabel(self, label) +#endif + + +//-- D3D11 C/C++ wrappers ------------------------------------------------------ +static inline HRESULT _sg_d3d11_CheckFormatSupport(ID3D11Device* self, DXGI_FORMAT Format, UINT* pFormatSupport) { + #if defined(__cplusplus) + return self->CheckFormatSupport(Format, pFormatSupport); + #else + return self->lpVtbl->CheckFormatSupport(self, Format, pFormatSupport); + #endif +} + +static inline void _sg_d3d11_OMSetRenderTargets(ID3D11DeviceContext* self, UINT NumViews, ID3D11RenderTargetView* const* ppRenderTargetViews, ID3D11DepthStencilView *pDepthStencilView) { + #if defined(__cplusplus) + self->OMSetRenderTargets(NumViews, ppRenderTargetViews, pDepthStencilView); + #else + self->lpVtbl->OMSetRenderTargets(self, NumViews, ppRenderTargetViews, pDepthStencilView); + #endif +} + +static inline void _sg_d3d11_RSSetState(ID3D11DeviceContext* self, ID3D11RasterizerState* pRasterizerState) { + #if defined(__cplusplus) + self->RSSetState(pRasterizerState); + #else + self->lpVtbl->RSSetState(self, pRasterizerState); + #endif +} + +static inline void _sg_d3d11_OMSetDepthStencilState(ID3D11DeviceContext* self, ID3D11DepthStencilState* pDepthStencilState, UINT StencilRef) { + #if defined(__cplusplus) + self->OMSetDepthStencilState(pDepthStencilState, StencilRef); + #else + self->lpVtbl->OMSetDepthStencilState(self, pDepthStencilState, StencilRef); + #endif +} + +static inline void _sg_d3d11_OMSetBlendState(ID3D11DeviceContext* self, ID3D11BlendState* pBlendState, const FLOAT BlendFactor[4], UINT SampleMask) { + #if defined(__cplusplus) + self->OMSetBlendState(pBlendState, BlendFactor, SampleMask); + #else + self->lpVtbl->OMSetBlendState(self, pBlendState, BlendFactor, SampleMask); + #endif +} + +static inline void _sg_d3d11_IASetVertexBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppVertexBuffers, const UINT* pStrides, const UINT* pOffsets) { + #if defined(__cplusplus) + self->IASetVertexBuffers(StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); + #else + self->lpVtbl->IASetVertexBuffers(self, StartSlot, NumBuffers, ppVertexBuffers, pStrides, pOffsets); + #endif +} + +static inline void _sg_d3d11_IASetIndexBuffer(ID3D11DeviceContext* self, ID3D11Buffer* pIndexBuffer, DXGI_FORMAT Format, UINT Offset) { + #if defined(__cplusplus) + self->IASetIndexBuffer(pIndexBuffer, Format, Offset); + #else + self->lpVtbl->IASetIndexBuffer(self, pIndexBuffer, Format, Offset); + #endif +} + +static inline void _sg_d3d11_IASetInputLayout(ID3D11DeviceContext* self, ID3D11InputLayout* pInputLayout) { + #if defined(__cplusplus) + self->IASetInputLayout(pInputLayout); + #else + self->lpVtbl->IASetInputLayout(self, pInputLayout); + #endif +} + +static inline void _sg_d3d11_VSSetShader(ID3D11DeviceContext* self, ID3D11VertexShader* pVertexShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { + #if defined(__cplusplus) + self->VSSetShader(pVertexShader, ppClassInstances, NumClassInstances); + #else + self->lpVtbl->VSSetShader(self, pVertexShader, ppClassInstances, NumClassInstances); + #endif +} + +static inline void _sg_d3d11_PSSetShader(ID3D11DeviceContext* self, ID3D11PixelShader* pPixelShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { + #if defined(__cplusplus) + self->PSSetShader(pPixelShader, ppClassInstances, NumClassInstances); + #else + self->lpVtbl->PSSetShader(self, pPixelShader, ppClassInstances, NumClassInstances); + #endif +} + +static inline void _sg_d3d11_CSSetShader(ID3D11DeviceContext* self, ID3D11ComputeShader* pComputeShader, ID3D11ClassInstance* const* ppClassInstances, UINT NumClassInstances) { + #if defined(__cplusplus) + self->CSSetShader(pComputeShader, ppClassInstances, NumClassInstances); + #else + self->lpVtbl->CSSetShader(self, pComputeShader, ppClassInstances, NumClassInstances); + #endif +} + +static inline void _sg_d3d11_VSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { + #if defined(__cplusplus) + self->VSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); + #else + self->lpVtbl->VSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); + #endif +} + +static inline void _sg_d3d11_PSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { + #if defined(__cplusplus) + self->PSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); + #else + self->lpVtbl->PSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); + #endif +} + +static inline void _sg_d3d11_CSSetConstantBuffers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumBuffers, ID3D11Buffer* const* ppConstantBuffers) { + #if defined(__cplusplus) + self->CSSetConstantBuffers(StartSlot, NumBuffers, ppConstantBuffers); + #else + self->lpVtbl->CSSetConstantBuffers(self, StartSlot, NumBuffers, ppConstantBuffers); + #endif +} + +static inline void _sg_d3d11_VSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { + #if defined(__cplusplus) + self->VSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); + #else + self->lpVtbl->VSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); + #endif +} + +static inline void _sg_d3d11_PSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { + #if defined(__cplusplus) + self->PSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); + #else + self->lpVtbl->PSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); + #endif +} + +static inline void _sg_d3d11_CSSetShaderResources(ID3D11DeviceContext* self, UINT StartSlot, UINT NumViews, ID3D11ShaderResourceView* const* ppShaderResourceViews) { + #if defined(__cplusplus) + self->CSSetShaderResources(StartSlot, NumViews, ppShaderResourceViews); + #else + self->lpVtbl->CSSetShaderResources(self, StartSlot, NumViews, ppShaderResourceViews); + #endif +} + +static inline void _sg_d3d11_VSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { + #if defined(__cplusplus) + self->VSSetSamplers(StartSlot, NumSamplers, ppSamplers); + #else + self->lpVtbl->VSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); + #endif +} + +static inline void _sg_d3d11_PSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { + #if defined(__cplusplus) + self->PSSetSamplers(StartSlot, NumSamplers, ppSamplers); + #else + self->lpVtbl->PSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); + #endif +} + +static inline void _sg_d3d11_CSSetSamplers(ID3D11DeviceContext* self, UINT StartSlot, UINT NumSamplers, ID3D11SamplerState* const* ppSamplers) { + #if defined(__cplusplus) + self->CSSetSamplers(StartSlot, NumSamplers, ppSamplers); + #else + self->lpVtbl->CSSetSamplers(self, StartSlot, NumSamplers, ppSamplers); + #endif +} + +static inline void _sg_d3d11_CSSetUnorderedAccessViews(ID3D11DeviceContext* self, UINT StartSlot, UINT NumUAVs, ID3D11UnorderedAccessView* const* ppUnorderedAccessViews, const UINT* pUAVInitialCounts) { + #if defined(__cplusplus) + self->CSSetUnorderedAccessViews(StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); + #else + self->lpVtbl->CSSetUnorderedAccessViews(self, StartSlot, NumUAVs, ppUnorderedAccessViews, pUAVInitialCounts); + #endif +} + +static inline HRESULT _sg_d3d11_CreateBuffer(ID3D11Device* self, const D3D11_BUFFER_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Buffer** ppBuffer) { + #if defined(__cplusplus) + return self->CreateBuffer(pDesc, pInitialData, ppBuffer); + #else + return self->lpVtbl->CreateBuffer(self, pDesc, pInitialData, ppBuffer); + #endif +} + +static inline HRESULT _sg_d3d11_CreateTexture2D(ID3D11Device* self, const D3D11_TEXTURE2D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture2D** ppTexture2D) { + #if defined(__cplusplus) + return self->CreateTexture2D(pDesc, pInitialData, ppTexture2D); + #else + return self->lpVtbl->CreateTexture2D(self, pDesc, pInitialData, ppTexture2D); + #endif +} + +static inline HRESULT _sg_d3d11_CreateShaderResourceView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_SHADER_RESOURCE_VIEW_DESC* pDesc, ID3D11ShaderResourceView** ppSRView) { + #if defined(__cplusplus) + return self->CreateShaderResourceView(pResource, pDesc, ppSRView); + #else + return self->lpVtbl->CreateShaderResourceView(self, pResource, pDesc, ppSRView); + #endif +} + +static inline HRESULT _sg_d3d11_CreateUnorderedAccessView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_UNORDERED_ACCESS_VIEW_DESC* pDesc, ID3D11UnorderedAccessView** ppUAVView) { + #if defined(__cplusplus) + return self->CreateUnorderedAccessView(pResource, pDesc, ppUAVView); + #else + return self->lpVtbl->CreateUnorderedAccessView(self, pResource, pDesc, ppUAVView); + #endif +} + +static inline void _sg_d3d11_GetResource(ID3D11View* self, ID3D11Resource** ppResource) { + #if defined(__cplusplus) + self->GetResource(ppResource); + #else + self->lpVtbl->GetResource(self, ppResource); + #endif +} + +static inline HRESULT _sg_d3d11_CreateTexture3D(ID3D11Device* self, const D3D11_TEXTURE3D_DESC* pDesc, const D3D11_SUBRESOURCE_DATA* pInitialData, ID3D11Texture3D** ppTexture3D) { + #if defined(__cplusplus) + return self->CreateTexture3D(pDesc, pInitialData, ppTexture3D); + #else + return self->lpVtbl->CreateTexture3D(self, pDesc, pInitialData, ppTexture3D); + #endif +} + +static inline HRESULT _sg_d3d11_CreateSamplerState(ID3D11Device* self, const D3D11_SAMPLER_DESC* pSamplerDesc, ID3D11SamplerState** ppSamplerState) { + #if defined(__cplusplus) + return self->CreateSamplerState(pSamplerDesc, ppSamplerState); + #else + return self->lpVtbl->CreateSamplerState(self, pSamplerDesc, ppSamplerState); + #endif +} + +static inline LPVOID _sg_d3d11_GetBufferPointer(ID3D10Blob* self) { + #if defined(__cplusplus) + return self->GetBufferPointer(); + #else + return self->lpVtbl->GetBufferPointer(self); + #endif +} + +static inline SIZE_T _sg_d3d11_GetBufferSize(ID3D10Blob* self) { + #if defined(__cplusplus) + return self->GetBufferSize(); + #else + return self->lpVtbl->GetBufferSize(self); + #endif +} + +static inline HRESULT _sg_d3d11_CreateVertexShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11VertexShader** ppVertexShader) { + #if defined(__cplusplus) + return self->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); + #else + return self->lpVtbl->CreateVertexShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader); + #endif +} + +static inline HRESULT _sg_d3d11_CreatePixelShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11PixelShader** ppPixelShader) { + #if defined(__cplusplus) + return self->CreatePixelShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); + #else + return self->lpVtbl->CreatePixelShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppPixelShader); + #endif +} + +static inline HRESULT _sg_d3d11_CreateComputeShader(ID3D11Device* self, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, ID3D11ComputeShader** ppComputeShader) { + #if defined(__cplusplus) + return self->CreateComputeShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader); + #else + return self->lpVtbl->CreateComputeShader(self, pShaderBytecode, BytecodeLength, pClassLinkage, ppComputeShader); + #endif +} + +static inline HRESULT _sg_d3d11_CreateInputLayout(ID3D11Device* self, const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs, UINT NumElements, const void* pShaderBytecodeWithInputSignature, SIZE_T BytecodeLength, ID3D11InputLayout **ppInputLayout) { + #if defined(__cplusplus) + return self->CreateInputLayout(pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); + #else + return self->lpVtbl->CreateInputLayout(self, pInputElementDescs, NumElements, pShaderBytecodeWithInputSignature, BytecodeLength, ppInputLayout); + #endif +} + +static inline HRESULT _sg_d3d11_CreateRasterizerState(ID3D11Device* self, const D3D11_RASTERIZER_DESC* pRasterizerDesc, ID3D11RasterizerState** ppRasterizerState) { + #if defined(__cplusplus) + return self->CreateRasterizerState(pRasterizerDesc, ppRasterizerState); + #else + return self->lpVtbl->CreateRasterizerState(self, pRasterizerDesc, ppRasterizerState); + #endif +} + +static inline HRESULT _sg_d3d11_CreateDepthStencilState(ID3D11Device* self, const D3D11_DEPTH_STENCIL_DESC* pDepthStencilDesc, ID3D11DepthStencilState** ppDepthStencilState) { + #if defined(__cplusplus) + return self->CreateDepthStencilState(pDepthStencilDesc, ppDepthStencilState); + #else + return self->lpVtbl->CreateDepthStencilState(self, pDepthStencilDesc, ppDepthStencilState); + #endif +} + +static inline HRESULT _sg_d3d11_CreateBlendState(ID3D11Device* self, const D3D11_BLEND_DESC* pBlendStateDesc, ID3D11BlendState** ppBlendState) { + #if defined(__cplusplus) + return self->CreateBlendState(pBlendStateDesc, ppBlendState); + #else + return self->lpVtbl->CreateBlendState(self, pBlendStateDesc, ppBlendState); + #endif +} + +static inline HRESULT _sg_d3d11_CreateRenderTargetView(ID3D11Device* self, ID3D11Resource *pResource, const D3D11_RENDER_TARGET_VIEW_DESC* pDesc, ID3D11RenderTargetView** ppRTView) { + #if defined(__cplusplus) + return self->CreateRenderTargetView(pResource, pDesc, ppRTView); + #else + return self->lpVtbl->CreateRenderTargetView(self, pResource, pDesc, ppRTView); + #endif +} + +static inline HRESULT _sg_d3d11_CreateDepthStencilView(ID3D11Device* self, ID3D11Resource* pResource, const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc, ID3D11DepthStencilView** ppDepthStencilView) { + #if defined(__cplusplus) + return self->CreateDepthStencilView(pResource, pDesc, ppDepthStencilView); + #else + return self->lpVtbl->CreateDepthStencilView(self, pResource, pDesc, ppDepthStencilView); + #endif +} + +static inline void _sg_d3d11_RSSetViewports(ID3D11DeviceContext* self, UINT NumViewports, const D3D11_VIEWPORT* pViewports) { + #if defined(__cplusplus) + self->RSSetViewports(NumViewports, pViewports); + #else + self->lpVtbl->RSSetViewports(self, NumViewports, pViewports); + #endif +} + +static inline void _sg_d3d11_RSSetScissorRects(ID3D11DeviceContext* self, UINT NumRects, const D3D11_RECT* pRects) { + #if defined(__cplusplus) + self->RSSetScissorRects(NumRects, pRects); + #else + self->lpVtbl->RSSetScissorRects(self, NumRects, pRects); + #endif +} + +static inline void _sg_d3d11_ClearRenderTargetView(ID3D11DeviceContext* self, ID3D11RenderTargetView* pRenderTargetView, const FLOAT ColorRGBA[4]) { + #if defined(__cplusplus) + self->ClearRenderTargetView(pRenderTargetView, ColorRGBA); + #else + self->lpVtbl->ClearRenderTargetView(self, pRenderTargetView, ColorRGBA); + #endif +} + +static inline void _sg_d3d11_ClearDepthStencilView(ID3D11DeviceContext* self, ID3D11DepthStencilView* pDepthStencilView, UINT ClearFlags, FLOAT Depth, UINT8 Stencil) { + #if defined(__cplusplus) + self->ClearDepthStencilView(pDepthStencilView, ClearFlags, Depth, Stencil); + #else + self->lpVtbl->ClearDepthStencilView(self, pDepthStencilView, ClearFlags, Depth, Stencil); + #endif +} + +static inline void _sg_d3d11_ResolveSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, ID3D11Resource* pSrcResource, UINT SrcSubresource, DXGI_FORMAT Format) { + #if defined(__cplusplus) + self->ResolveSubresource(pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #else + self->lpVtbl->ResolveSubresource(self, pDstResource, DstSubresource, pSrcResource, SrcSubresource, Format); + #endif +} + +static inline void _sg_d3d11_IASetPrimitiveTopology(ID3D11DeviceContext* self, D3D11_PRIMITIVE_TOPOLOGY Topology) { + #if defined(__cplusplus) + self->IASetPrimitiveTopology(Topology); + #else + self->lpVtbl->IASetPrimitiveTopology(self, Topology); + #endif +} + +static inline void _sg_d3d11_UpdateSubresource(ID3D11DeviceContext* self, ID3D11Resource* pDstResource, UINT DstSubresource, const D3D11_BOX* pDstBox, const void* pSrcData, UINT SrcRowPitch, UINT SrcDepthPitch) { + #if defined(__cplusplus) + self->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); + #else + self->lpVtbl->UpdateSubresource(self, pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch); + #endif +} + +static inline void _sg_d3d11_DrawIndexed(ID3D11DeviceContext* self, UINT IndexCount, UINT StartIndexLocation, INT BaseVertexLocation) { + #if defined(__cplusplus) + self->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation); + #else + self->lpVtbl->DrawIndexed(self, IndexCount, StartIndexLocation, BaseVertexLocation); + #endif +} + +static inline void _sg_d3d11_DrawIndexedInstanced(ID3D11DeviceContext* self, UINT IndexCountPerInstance, UINT InstanceCount, UINT StartIndexLocation, INT BaseVertexLocation, UINT StartInstanceLocation) { + #if defined(__cplusplus) + self->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); + #else + self->lpVtbl->DrawIndexedInstanced(self, IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation); + #endif +} + +static inline void _sg_d3d11_Draw(ID3D11DeviceContext* self, UINT VertexCount, UINT StartVertexLocation) { + #if defined(__cplusplus) + self->Draw(VertexCount, StartVertexLocation); + #else + self->lpVtbl->Draw(self, VertexCount, StartVertexLocation); + #endif +} + +static inline void _sg_d3d11_DrawInstanced(ID3D11DeviceContext* self, UINT VertexCountPerInstance, UINT InstanceCount, UINT StartVertexLocation, UINT StartInstanceLocation) { + #if defined(__cplusplus) + self->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); + #else + self->lpVtbl->DrawInstanced(self, VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation); + #endif +} + +static inline void _sg_d3d11_Dispatch(ID3D11DeviceContext* self, UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ) { + #if defined(__cplusplus) + self->Dispatch(ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); + #else + self->lpVtbl->Dispatch(self, ThreadGroupCountX, ThreadGroupCountY, ThreadGroupCountZ); + #endif +} + +static inline HRESULT _sg_d3d11_Map(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource, D3D11_MAP MapType, UINT MapFlags, D3D11_MAPPED_SUBRESOURCE* pMappedResource) { + #if defined(__cplusplus) + return self->Map(pResource, Subresource, MapType, MapFlags, pMappedResource); + #else + return self->lpVtbl->Map(self, pResource, Subresource, MapType, MapFlags, pMappedResource); + #endif +} + +static inline void _sg_d3d11_Unmap(ID3D11DeviceContext* self, ID3D11Resource* pResource, UINT Subresource) { + #if defined(__cplusplus) + self->Unmap(pResource, Subresource); + #else + self->lpVtbl->Unmap(self, pResource, Subresource); + #endif +} + +static inline void _sg_d3d11_ClearState(ID3D11DeviceContext* self) { + #if defined(__cplusplus) + self->ClearState(); + #else + self->lpVtbl->ClearState(self); + #endif +} + +//-- enum translation functions ------------------------------------------------ +_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_usage(sg_usage usg) { + switch (usg) { + case SG_USAGE_IMMUTABLE: + return D3D11_USAGE_IMMUTABLE; + case SG_USAGE_DYNAMIC: + case SG_USAGE_STREAM: + return D3D11_USAGE_DYNAMIC; + default: + SOKOL_UNREACHABLE; + return (D3D11_USAGE) 0; + } +} + +_SOKOL_PRIVATE D3D11_USAGE _sg_d3d11_buffer_usage(sg_usage usg, sg_buffer_type type) { + switch (usg) { + case SG_USAGE_IMMUTABLE: + if (type == SG_BUFFERTYPE_STORAGEBUFFER) { + return D3D11_USAGE_DEFAULT; + } else { + return D3D11_USAGE_IMMUTABLE; + } + case SG_USAGE_DYNAMIC: + case SG_USAGE_STREAM: + return D3D11_USAGE_DYNAMIC; + default: + SOKOL_UNREACHABLE; + return (D3D11_USAGE) 0; + } +} + +_SOKOL_PRIVATE UINT _sg_d3d11_buffer_bind_flags(sg_usage usg, sg_buffer_type t) { + switch (t) { + case SG_BUFFERTYPE_VERTEXBUFFER: + return D3D11_BIND_VERTEX_BUFFER; + case SG_BUFFERTYPE_INDEXBUFFER: + return D3D11_BIND_INDEX_BUFFER; + case SG_BUFFERTYPE_STORAGEBUFFER: + if (usg == SG_USAGE_IMMUTABLE) { + return D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + } else { + return D3D11_BIND_SHADER_RESOURCE; + } + default: + SOKOL_UNREACHABLE; + return 0; + } +} + +_SOKOL_PRIVATE UINT _sg_d3d11_buffer_misc_flags(sg_buffer_type t) { + switch (t) { + case SG_BUFFERTYPE_VERTEXBUFFER: + case SG_BUFFERTYPE_INDEXBUFFER: + return 0; + case SG_BUFFERTYPE_STORAGEBUFFER: + return D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS; + default: + SOKOL_UNREACHABLE; + return 0; + } +} + +_SOKOL_PRIVATE UINT _sg_d3d11_cpu_access_flags(sg_usage usg) { + switch (usg) { + case SG_USAGE_IMMUTABLE: + return 0; + case SG_USAGE_DYNAMIC: + case SG_USAGE_STREAM: + return D3D11_CPU_ACCESS_WRITE; + default: + SOKOL_UNREACHABLE; + return 0; + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_texture_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return DXGI_FORMAT_R8_UNORM; + case SG_PIXELFORMAT_R8SN: return DXGI_FORMAT_R8_SNORM; + case SG_PIXELFORMAT_R8UI: return DXGI_FORMAT_R8_UINT; + case SG_PIXELFORMAT_R8SI: return DXGI_FORMAT_R8_SINT; + case SG_PIXELFORMAT_R16: return DXGI_FORMAT_R16_UNORM; + case SG_PIXELFORMAT_R16SN: return DXGI_FORMAT_R16_SNORM; + case SG_PIXELFORMAT_R16UI: return DXGI_FORMAT_R16_UINT; + case SG_PIXELFORMAT_R16SI: return DXGI_FORMAT_R16_SINT; + case SG_PIXELFORMAT_R16F: return DXGI_FORMAT_R16_FLOAT; + case SG_PIXELFORMAT_RG8: return DXGI_FORMAT_R8G8_UNORM; + case SG_PIXELFORMAT_RG8SN: return DXGI_FORMAT_R8G8_SNORM; + case SG_PIXELFORMAT_RG8UI: return DXGI_FORMAT_R8G8_UINT; + case SG_PIXELFORMAT_RG8SI: return DXGI_FORMAT_R8G8_SINT; + case SG_PIXELFORMAT_R32UI: return DXGI_FORMAT_R32_UINT; + case SG_PIXELFORMAT_R32SI: return DXGI_FORMAT_R32_SINT; + case SG_PIXELFORMAT_R32F: return DXGI_FORMAT_R32_FLOAT; + case SG_PIXELFORMAT_RG16: return DXGI_FORMAT_R16G16_UNORM; + case SG_PIXELFORMAT_RG16SN: return DXGI_FORMAT_R16G16_SNORM; + case SG_PIXELFORMAT_RG16UI: return DXGI_FORMAT_R16G16_UINT; + case SG_PIXELFORMAT_RG16SI: return DXGI_FORMAT_R16G16_SINT; + case SG_PIXELFORMAT_RG16F: return DXGI_FORMAT_R16G16_FLOAT; + case SG_PIXELFORMAT_RGBA8: return DXGI_FORMAT_R8G8B8A8_UNORM; + case SG_PIXELFORMAT_SRGB8A8: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case SG_PIXELFORMAT_RGBA8SN: return DXGI_FORMAT_R8G8B8A8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return DXGI_FORMAT_R8G8B8A8_UINT; + case SG_PIXELFORMAT_RGBA8SI: return DXGI_FORMAT_R8G8B8A8_SINT; + case SG_PIXELFORMAT_BGRA8: return DXGI_FORMAT_B8G8R8A8_UNORM; + case SG_PIXELFORMAT_RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_PIXELFORMAT_RG11B10F: return DXGI_FORMAT_R11G11B10_FLOAT; + case SG_PIXELFORMAT_RGB9E5: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP; + case SG_PIXELFORMAT_RG32UI: return DXGI_FORMAT_R32G32_UINT; + case SG_PIXELFORMAT_RG32SI: return DXGI_FORMAT_R32G32_SINT; + case SG_PIXELFORMAT_RG32F: return DXGI_FORMAT_R32G32_FLOAT; + case SG_PIXELFORMAT_RGBA16: return DXGI_FORMAT_R16G16B16A16_UNORM; + case SG_PIXELFORMAT_RGBA16SN: return DXGI_FORMAT_R16G16B16A16_SNORM; + case SG_PIXELFORMAT_RGBA16UI: return DXGI_FORMAT_R16G16B16A16_UINT; + case SG_PIXELFORMAT_RGBA16SI: return DXGI_FORMAT_R16G16B16A16_SINT; + case SG_PIXELFORMAT_RGBA16F: return DXGI_FORMAT_R16G16B16A16_FLOAT; + case SG_PIXELFORMAT_RGBA32UI: return DXGI_FORMAT_R32G32B32A32_UINT; + case SG_PIXELFORMAT_RGBA32SI: return DXGI_FORMAT_R32G32B32A32_SINT; + case SG_PIXELFORMAT_RGBA32F: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case SG_PIXELFORMAT_DEPTH: return DXGI_FORMAT_R32_TYPELESS; + case SG_PIXELFORMAT_DEPTH_STENCIL: return DXGI_FORMAT_R24G8_TYPELESS; + case SG_PIXELFORMAT_BC1_RGBA: return DXGI_FORMAT_BC1_UNORM; + case SG_PIXELFORMAT_BC2_RGBA: return DXGI_FORMAT_BC2_UNORM; + case SG_PIXELFORMAT_BC3_RGBA: return DXGI_FORMAT_BC3_UNORM; + case SG_PIXELFORMAT_BC3_SRGBA: return DXGI_FORMAT_BC3_UNORM_SRGB; + case SG_PIXELFORMAT_BC4_R: return DXGI_FORMAT_BC4_UNORM; + case SG_PIXELFORMAT_BC4_RSN: return DXGI_FORMAT_BC4_SNORM; + case SG_PIXELFORMAT_BC5_RG: return DXGI_FORMAT_BC5_UNORM; + case SG_PIXELFORMAT_BC5_RGSN: return DXGI_FORMAT_BC5_SNORM; + case SG_PIXELFORMAT_BC6H_RGBF: return DXGI_FORMAT_BC6H_SF16; + case SG_PIXELFORMAT_BC6H_RGBUF: return DXGI_FORMAT_BC6H_UF16; + case SG_PIXELFORMAT_BC7_RGBA: return DXGI_FORMAT_BC7_UNORM; + case SG_PIXELFORMAT_BC7_SRGBA: return DXGI_FORMAT_BC7_UNORM_SRGB; + default: return DXGI_FORMAT_UNKNOWN; + }; +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_srv_pixel_format(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_R32_FLOAT; + } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + } else { + return _sg_d3d11_texture_pixel_format(fmt); + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_dsv_pixel_format(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_D32_FLOAT; + } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_D24_UNORM_S8_UINT; + } else { + return _sg_d3d11_texture_pixel_format(fmt); + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_rtv_pixel_format(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_R32_FLOAT; + } else if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + } else { + return _sg_d3d11_texture_pixel_format(fmt); + } +} + +_SOKOL_PRIVATE D3D11_PRIMITIVE_TOPOLOGY _sg_d3d11_primitive_topology(sg_primitive_type prim_type) { + switch (prim_type) { + case SG_PRIMITIVETYPE_POINTS: return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; + case SG_PRIMITIVETYPE_LINES: return D3D11_PRIMITIVE_TOPOLOGY_LINELIST; + case SG_PRIMITIVETYPE_LINE_STRIP: return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP; + case SG_PRIMITIVETYPE_TRIANGLES: return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + default: SOKOL_UNREACHABLE; return (D3D11_PRIMITIVE_TOPOLOGY) 0; + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_index_format(sg_index_type index_type) { + switch (index_type) { + case SG_INDEXTYPE_NONE: return DXGI_FORMAT_UNKNOWN; + case SG_INDEXTYPE_UINT16: return DXGI_FORMAT_R16_UINT; + case SG_INDEXTYPE_UINT32: return DXGI_FORMAT_R32_UINT; + default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; + } +} + +_SOKOL_PRIVATE D3D11_FILTER _sg_d3d11_filter(sg_filter min_f, sg_filter mag_f, sg_filter mipmap_f, bool comparison, uint32_t max_anisotropy) { + uint32_t d3d11_filter = 0; + if (max_anisotropy > 1) { + // D3D11_FILTER_ANISOTROPIC = 0x55, + d3d11_filter |= 0x55; + } else { + // D3D11_FILTER_MIN_MAG_MIP_POINT = 0, + // D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR = 0x1, + // D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x4, + // D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR = 0x5, + // D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT = 0x10, + // D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x11, + // D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT = 0x14, + // D3D11_FILTER_MIN_MAG_MIP_LINEAR = 0x15, + if (mipmap_f == SG_FILTER_LINEAR) { + d3d11_filter |= 0x01; + } + if (mag_f == SG_FILTER_LINEAR) { + d3d11_filter |= 0x04; + } + if (min_f == SG_FILTER_LINEAR) { + d3d11_filter |= 0x10; + } + } + // D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT = 0x80, + // D3D11_FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR = 0x81, + // D3D11_FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT = 0x84, + // D3D11_FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR = 0x85, + // D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT = 0x90, + // D3D11_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR = 0x91, + // D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT = 0x94, + // D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR = 0x95, + // D3D11_FILTER_COMPARISON_ANISOTROPIC = 0xd5, + if (comparison) { + d3d11_filter |= 0x80; + } + return (D3D11_FILTER)d3d11_filter; +} + +_SOKOL_PRIVATE D3D11_TEXTURE_ADDRESS_MODE _sg_d3d11_address_mode(sg_wrap m) { + switch (m) { + case SG_WRAP_REPEAT: return D3D11_TEXTURE_ADDRESS_WRAP; + case SG_WRAP_CLAMP_TO_EDGE: return D3D11_TEXTURE_ADDRESS_CLAMP; + case SG_WRAP_CLAMP_TO_BORDER: return D3D11_TEXTURE_ADDRESS_BORDER; + case SG_WRAP_MIRRORED_REPEAT: return D3D11_TEXTURE_ADDRESS_MIRROR; + default: SOKOL_UNREACHABLE; return (D3D11_TEXTURE_ADDRESS_MODE) 0; + } +} + +_SOKOL_PRIVATE DXGI_FORMAT _sg_d3d11_vertex_format(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return DXGI_FORMAT_R32_FLOAT; + case SG_VERTEXFORMAT_FLOAT2: return DXGI_FORMAT_R32G32_FLOAT; + case SG_VERTEXFORMAT_FLOAT3: return DXGI_FORMAT_R32G32B32_FLOAT; + case SG_VERTEXFORMAT_FLOAT4: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case SG_VERTEXFORMAT_INT: return DXGI_FORMAT_R32_SINT; + case SG_VERTEXFORMAT_INT2: return DXGI_FORMAT_R32G32_SINT; + case SG_VERTEXFORMAT_INT3: return DXGI_FORMAT_R32G32B32_SINT; + case SG_VERTEXFORMAT_INT4: return DXGI_FORMAT_R32G32B32A32_SINT; + case SG_VERTEXFORMAT_UINT: return DXGI_FORMAT_R32_UINT; + case SG_VERTEXFORMAT_UINT2: return DXGI_FORMAT_R32G32_UINT; + case SG_VERTEXFORMAT_UINT3: return DXGI_FORMAT_R32G32B32_UINT; + case SG_VERTEXFORMAT_UINT4: return DXGI_FORMAT_R32G32B32A32_UINT; + case SG_VERTEXFORMAT_BYTE4: return DXGI_FORMAT_R8G8B8A8_SINT; + case SG_VERTEXFORMAT_BYTE4N: return DXGI_FORMAT_R8G8B8A8_SNORM; + case SG_VERTEXFORMAT_UBYTE4: return DXGI_FORMAT_R8G8B8A8_UINT; + case SG_VERTEXFORMAT_UBYTE4N: return DXGI_FORMAT_R8G8B8A8_UNORM; + case SG_VERTEXFORMAT_SHORT2: return DXGI_FORMAT_R16G16_SINT; + case SG_VERTEXFORMAT_SHORT2N: return DXGI_FORMAT_R16G16_SNORM; + case SG_VERTEXFORMAT_USHORT2: return DXGI_FORMAT_R16G16_UINT; + case SG_VERTEXFORMAT_USHORT2N: return DXGI_FORMAT_R16G16_UNORM; + case SG_VERTEXFORMAT_SHORT4: return DXGI_FORMAT_R16G16B16A16_SINT; + case SG_VERTEXFORMAT_SHORT4N: return DXGI_FORMAT_R16G16B16A16_SNORM; + case SG_VERTEXFORMAT_USHORT4: return DXGI_FORMAT_R16G16B16A16_UINT; + case SG_VERTEXFORMAT_USHORT4N: return DXGI_FORMAT_R16G16B16A16_UNORM; + case SG_VERTEXFORMAT_UINT10_N2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_VERTEXFORMAT_HALF2: return DXGI_FORMAT_R16G16_FLOAT; + case SG_VERTEXFORMAT_HALF4: return DXGI_FORMAT_R16G16B16A16_FLOAT; + default: SOKOL_UNREACHABLE; return (DXGI_FORMAT) 0; + } +} + +_SOKOL_PRIVATE D3D11_INPUT_CLASSIFICATION _sg_d3d11_input_classification(sg_vertex_step step) { + switch (step) { + case SG_VERTEXSTEP_PER_VERTEX: return D3D11_INPUT_PER_VERTEX_DATA; + case SG_VERTEXSTEP_PER_INSTANCE: return D3D11_INPUT_PER_INSTANCE_DATA; + default: SOKOL_UNREACHABLE; return (D3D11_INPUT_CLASSIFICATION) 0; + } +} + +_SOKOL_PRIVATE D3D11_CULL_MODE _sg_d3d11_cull_mode(sg_cull_mode m) { + switch (m) { + case SG_CULLMODE_NONE: return D3D11_CULL_NONE; + case SG_CULLMODE_FRONT: return D3D11_CULL_FRONT; + case SG_CULLMODE_BACK: return D3D11_CULL_BACK; + default: SOKOL_UNREACHABLE; return (D3D11_CULL_MODE) 0; + } +} + +_SOKOL_PRIVATE D3D11_COMPARISON_FUNC _sg_d3d11_compare_func(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return D3D11_COMPARISON_NEVER; + case SG_COMPAREFUNC_LESS: return D3D11_COMPARISON_LESS; + case SG_COMPAREFUNC_EQUAL: return D3D11_COMPARISON_EQUAL; + case SG_COMPAREFUNC_LESS_EQUAL: return D3D11_COMPARISON_LESS_EQUAL; + case SG_COMPAREFUNC_GREATER: return D3D11_COMPARISON_GREATER; + case SG_COMPAREFUNC_NOT_EQUAL: return D3D11_COMPARISON_NOT_EQUAL; + case SG_COMPAREFUNC_GREATER_EQUAL: return D3D11_COMPARISON_GREATER_EQUAL; + case SG_COMPAREFUNC_ALWAYS: return D3D11_COMPARISON_ALWAYS; + default: SOKOL_UNREACHABLE; return (D3D11_COMPARISON_FUNC) 0; + } +} + +_SOKOL_PRIVATE D3D11_STENCIL_OP _sg_d3d11_stencil_op(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return D3D11_STENCIL_OP_KEEP; + case SG_STENCILOP_ZERO: return D3D11_STENCIL_OP_ZERO; + case SG_STENCILOP_REPLACE: return D3D11_STENCIL_OP_REPLACE; + case SG_STENCILOP_INCR_CLAMP: return D3D11_STENCIL_OP_INCR_SAT; + case SG_STENCILOP_DECR_CLAMP: return D3D11_STENCIL_OP_DECR_SAT; + case SG_STENCILOP_INVERT: return D3D11_STENCIL_OP_INVERT; + case SG_STENCILOP_INCR_WRAP: return D3D11_STENCIL_OP_INCR; + case SG_STENCILOP_DECR_WRAP: return D3D11_STENCIL_OP_DECR; + default: SOKOL_UNREACHABLE; return (D3D11_STENCIL_OP) 0; + } +} + +_SOKOL_PRIVATE D3D11_BLEND _sg_d3d11_blend_factor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return D3D11_BLEND_ZERO; + case SG_BLENDFACTOR_ONE: return D3D11_BLEND_ONE; + case SG_BLENDFACTOR_SRC_COLOR: return D3D11_BLEND_SRC_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return D3D11_BLEND_INV_SRC_COLOR; + case SG_BLENDFACTOR_SRC_ALPHA: return D3D11_BLEND_SRC_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return D3D11_BLEND_INV_SRC_ALPHA; + case SG_BLENDFACTOR_DST_COLOR: return D3D11_BLEND_DEST_COLOR; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return D3D11_BLEND_INV_DEST_COLOR; + case SG_BLENDFACTOR_DST_ALPHA: return D3D11_BLEND_DEST_ALPHA; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return D3D11_BLEND_INV_DEST_ALPHA; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return D3D11_BLEND_SRC_ALPHA_SAT; + case SG_BLENDFACTOR_BLEND_COLOR: return D3D11_BLEND_BLEND_FACTOR; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return D3D11_BLEND_INV_BLEND_FACTOR; + case SG_BLENDFACTOR_BLEND_ALPHA: return D3D11_BLEND_BLEND_FACTOR; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return D3D11_BLEND_INV_BLEND_FACTOR; + default: SOKOL_UNREACHABLE; return (D3D11_BLEND) 0; + } +} + +_SOKOL_PRIVATE D3D11_BLEND_OP _sg_d3d11_blend_op(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return D3D11_BLEND_OP_ADD; + case SG_BLENDOP_SUBTRACT: return D3D11_BLEND_OP_SUBTRACT; + case SG_BLENDOP_REVERSE_SUBTRACT: return D3D11_BLEND_OP_REV_SUBTRACT; + case SG_BLENDOP_MIN: return D3D11_BLEND_OP_MIN; + case SG_BLENDOP_MAX: return D3D11_BLEND_OP_MAX; + default: SOKOL_UNREACHABLE; return (D3D11_BLEND_OP) 0; + } +} + +_SOKOL_PRIVATE UINT8 _sg_d3d11_color_write_mask(sg_color_mask m) { + UINT8 res = 0; + if (m & SG_COLORMASK_R) { + res |= D3D11_COLOR_WRITE_ENABLE_RED; + } + if (m & SG_COLORMASK_G) { + res |= D3D11_COLOR_WRITE_ENABLE_GREEN; + } + if (m & SG_COLORMASK_B) { + res |= D3D11_COLOR_WRITE_ENABLE_BLUE; + } + if (m & SG_COLORMASK_A) { + res |= D3D11_COLOR_WRITE_ENABLE_ALPHA; + } + return res; +} + +_SOKOL_PRIVATE UINT _sg_d3d11_dxgi_fmt_caps(DXGI_FORMAT dxgi_fmt) { + UINT dxgi_fmt_caps = 0; + if (dxgi_fmt != DXGI_FORMAT_UNKNOWN) { + HRESULT hr = _sg_d3d11_CheckFormatSupport(_sg.d3d11.dev, dxgi_fmt, &dxgi_fmt_caps); + SOKOL_ASSERT(SUCCEEDED(hr) || (E_FAIL == hr)); + if (!SUCCEEDED(hr)) { + dxgi_fmt_caps = 0; + } + } + return dxgi_fmt_caps; +} + +// see: https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits#resource-limits-for-feature-level-11-hardware +_SOKOL_PRIVATE void _sg_d3d11_init_caps(void) { + _sg.backend = SG_BACKEND_D3D11; + + _sg.features.origin_top_left = true; + _sg.features.image_clamp_to_border = true; + _sg.features.mrt_independent_blend_state = true; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = true; + _sg.features.msaa_image_bindings = true; + + _sg.limits.max_image_size_2d = 16 * 1024; + _sg.limits.max_image_size_cube = 16 * 1024; + _sg.limits.max_image_size_3d = 2 * 1024; + _sg.limits.max_image_size_array = 16 * 1024; + _sg.limits.max_image_array_layers = 2 * 1024; + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + + // see: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_format_support + for (int fmt = (SG_PIXELFORMAT_NONE+1); fmt < _SG_PIXELFORMAT_NUM; fmt++) { + const UINT srv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_srv_pixel_format((sg_pixel_format)fmt)); + const UINT rtv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_rtv_pixel_format((sg_pixel_format)fmt)); + const UINT dsv_dxgi_fmt_caps = _sg_d3d11_dxgi_fmt_caps(_sg_d3d11_dsv_pixel_format((sg_pixel_format)fmt)); + _sg_pixelformat_info_t* info = &_sg.formats[fmt]; + const bool render = 0 != (rtv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_RENDER_TARGET); + const bool depth = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL); + info->sample = 0 != (srv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_TEXTURE2D); + info->filter = 0 != (srv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_SHADER_SAMPLE); + info->render = render || depth; + if (depth) { + info->blend = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE); + info->msaa = 0 != (dsv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET); + } else { + info->blend = 0 != (rtv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_BLENDABLE); + info->msaa = 0 != (rtv_dxgi_fmt_caps & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET); + } + info->depth = depth; + } +} + +_SOKOL_PRIVATE void _sg_d3d11_setup_backend(const sg_desc* desc) { + // assume _sg.d3d11 already is zero-initialized + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->environment.d3d11.device); + SOKOL_ASSERT(desc->environment.d3d11.device_context); + _sg.d3d11.valid = true; + _sg.d3d11.dev = (ID3D11Device*) desc->environment.d3d11.device; + _sg.d3d11.ctx = (ID3D11DeviceContext*) desc->environment.d3d11.device_context; + _sg_d3d11_init_caps(); +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_backend(void) { + SOKOL_ASSERT(_sg.d3d11.valid); + _sg.d3d11.valid = false; +} + +_SOKOL_PRIVATE void _sg_d3d11_clear_state(void) { + // clear all the device context state, so that resource refs don't keep stuck in the d3d device context + _sg_d3d11_ClearState(_sg.d3d11.ctx); +} + +_SOKOL_PRIVATE void _sg_d3d11_reset_state_cache(void) { + // there's currently no state cache in the D3D11 backend, so this is a no-op +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + SOKOL_ASSERT(!buf->d3d11.buf); + const bool injected = (0 != desc->d3d11_buffer); + if (injected) { + buf->d3d11.buf = (ID3D11Buffer*) desc->d3d11_buffer; + _sg_d3d11_AddRef(buf->d3d11.buf); + // FIXME: for storage buffers also need to inject resource view + } else { + D3D11_BUFFER_DESC d3d11_buf_desc; + _sg_clear(&d3d11_buf_desc, sizeof(d3d11_buf_desc)); + d3d11_buf_desc.ByteWidth = (UINT)buf->cmn.size; + d3d11_buf_desc.Usage = _sg_d3d11_buffer_usage(buf->cmn.usage, buf->cmn.type); + d3d11_buf_desc.BindFlags = _sg_d3d11_buffer_bind_flags(buf->cmn.usage, buf->cmn.type); + d3d11_buf_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(buf->cmn.usage); + d3d11_buf_desc.MiscFlags = _sg_d3d11_buffer_misc_flags(buf->cmn.type); + D3D11_SUBRESOURCE_DATA* init_data_ptr = 0; + D3D11_SUBRESOURCE_DATA init_data; + _sg_clear(&init_data, sizeof(init_data)); + if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { + // D3D11 doesn't allow creating immutable buffers without data, so need + // to explicitly provide a zero-initialized memory buffer + if (desc->data.ptr) { + init_data.pSysMem = desc->data.ptr; + } else { + init_data.pSysMem = (const void*)_sg_malloc_clear((size_t)buf->cmn.size); + } + init_data_ptr = &init_data; + } + HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_buf_desc, init_data_ptr, &buf->d3d11.buf); + if (init_data.pSysMem && (desc->data.ptr == 0)) { + _sg_free((void*)init_data.pSysMem); + } + if (!(SUCCEEDED(hr) && buf->d3d11.buf)) { + _SG_ERROR(D3D11_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + + // for storage buffers need to create a shader-resource-view + // for read-only access, and an unordered-access-view for + // read-write access + if (buf->cmn.type == SG_BUFFERTYPE_STORAGEBUFFER) { + SOKOL_ASSERT(_sg_multiple_u64((uint64_t)buf->cmn.size, 4)); + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; + _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); + d3d11_srv_desc.Format = DXGI_FORMAT_R32_TYPELESS; + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX; + d3d11_srv_desc.BufferEx.FirstElement = 0; + d3d11_srv_desc.BufferEx.NumElements = ((UINT)buf->cmn.size) / 4; + d3d11_srv_desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW; + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_srv_desc, &buf->d3d11.srv); + if (!(SUCCEEDED(hr) && buf->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_BUFFER_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + if (buf->cmn.usage == SG_USAGE_IMMUTABLE) { + D3D11_UNORDERED_ACCESS_VIEW_DESC d3d11_uav_desc; + _sg_clear(&d3d11_uav_desc, sizeof(d3d11_uav_desc)); + d3d11_uav_desc.Format = DXGI_FORMAT_R32_TYPELESS; + d3d11_uav_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + d3d11_uav_desc.Buffer.FirstElement = 0; + d3d11_uav_desc.Buffer.NumElements = ((UINT)buf->cmn.size) / 4; + d3d11_uav_desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; + hr = _sg_d3d11_CreateUnorderedAccessView(_sg.d3d11.dev, (ID3D11Resource*)buf->d3d11.buf, &d3d11_uav_desc, &buf->d3d11.uav); + if (!(SUCCEEDED(hr) && buf->d3d11.uav)) { + _SG_ERROR(D3D11_CREATE_BUFFER_UAV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + } + _sg_d3d11_setlabel(buf->d3d11.buf, desc->label); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + if (buf->d3d11.buf) { + _sg_d3d11_Release(buf->d3d11.buf); + } + if (buf->d3d11.srv) { + _sg_d3d11_Release(buf->d3d11.srv); + } + if (buf->d3d11.uav) { + _sg_d3d11_Release(buf->d3d11.uav); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_fill_subres_data(const _sg_image_t* img, const sg_image_data* data) { + const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; + int subres_index = 0; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) { + SOKOL_ASSERT(subres_index < (SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS)); + D3D11_SUBRESOURCE_DATA* subres_data = &_sg.d3d11.subres_data[subres_index]; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + const sg_range* subimg_data = &(data->subimage[face_index][mip_index]); + const size_t slice_size = subimg_data->size / (size_t)num_slices; + const size_t slice_offset = slice_size * (size_t)slice_index; + const uint8_t* ptr = (const uint8_t*) subimg_data->ptr; + subres_data->pSysMem = ptr + slice_offset; + subres_data->SysMemPitch = (UINT)_sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + if (img->cmn.type == SG_IMAGETYPE_3D) { + // FIXME? const int mip_depth = _sg_miplevel_dim(img->depth, mip_index); + subres_data->SysMemSlicePitch = (UINT)_sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + } else { + subres_data->SysMemSlicePitch = 0; + } + } + } + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + SOKOL_ASSERT((0 == img->d3d11.tex2d) && (0 == img->d3d11.tex3d) && (0 == img->d3d11.res) && (0 == img->d3d11.srv)); + HRESULT hr; + + const bool injected = (0 != desc->d3d11_texture); + const bool msaa = (img->cmn.sample_count > 1); + SOKOL_ASSERT(!(msaa && (img->cmn.type == SG_IMAGETYPE_CUBE))); + img->d3d11.format = _sg_d3d11_texture_pixel_format(img->cmn.pixel_format); + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { + _SG_ERROR(D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + + // prepare initial content pointers + D3D11_SUBRESOURCE_DATA* init_data = 0; + if (!injected && (img->cmn.usage == SG_USAGE_IMMUTABLE) && !img->cmn.render_target) { + _sg_d3d11_fill_subres_data(img, &desc->data); + init_data = _sg.d3d11.subres_data; + } + if (img->cmn.type != SG_IMAGETYPE_3D) { + // 2D-, cube- or array-texture + // first check for injected texture and/or resource view + if (injected) { + img->d3d11.tex2d = (ID3D11Texture2D*) desc->d3d11_texture; + _sg_d3d11_AddRef(img->d3d11.tex2d); + img->d3d11.srv = (ID3D11ShaderResourceView*) desc->d3d11_shader_resource_view; + if (img->d3d11.srv) { + _sg_d3d11_AddRef(img->d3d11.srv); + } + } else { + // if not injected, create 2D texture + D3D11_TEXTURE2D_DESC d3d11_tex_desc; + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; + switch (img->cmn.type) { + case SG_IMAGETYPE_ARRAY: d3d11_tex_desc.ArraySize = (UINT)img->cmn.num_slices; break; + case SG_IMAGETYPE_CUBE: d3d11_tex_desc.ArraySize = 6; break; + default: d3d11_tex_desc.ArraySize = 1; break; + } + d3d11_tex_desc.Format = img->d3d11.format; + d3d11_tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + if (img->cmn.render_target) { + d3d11_tex_desc.Usage = D3D11_USAGE_DEFAULT; + if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) { + d3d11_tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL; + } else { + d3d11_tex_desc.BindFlags |= D3D11_BIND_RENDER_TARGET; + } + d3d11_tex_desc.CPUAccessFlags = 0; + } else { + d3d11_tex_desc.Usage = _sg_d3d11_usage(img->cmn.usage); + d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); + } + d3d11_tex_desc.SampleDesc.Count = (UINT)img->cmn.sample_count; + d3d11_tex_desc.SampleDesc.Quality = (UINT) (msaa ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0); + d3d11_tex_desc.MiscFlags = (img->cmn.type == SG_IMAGETYPE_CUBE) ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0; + + hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex2d); + if (!(SUCCEEDED(hr) && img->d3d11.tex2d)) { + _SG_ERROR(D3D11_CREATE_2D_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.tex2d, desc->label); + + // create shader-resource-view for 2D texture + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; + _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); + d3d11_srv_desc.Format = _sg_d3d11_srv_pixel_format(img->cmn.pixel_format); + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + d3d11_srv_desc.ViewDimension = msaa ? D3D11_SRV_DIMENSION_TEXTURE2DMS : D3D11_SRV_DIMENSION_TEXTURE2D; + d3d11_srv_desc.Texture2D.MipLevels = (UINT)img->cmn.num_mipmaps; + break; + case SG_IMAGETYPE_CUBE: + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; + d3d11_srv_desc.TextureCube.MipLevels = (UINT)img->cmn.num_mipmaps; + break; + case SG_IMAGETYPE_ARRAY: + d3d11_srv_desc.ViewDimension = msaa ? D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D11_SRV_DIMENSION_TEXTURE2DARRAY; + d3d11_srv_desc.Texture2DArray.MipLevels = (UINT)img->cmn.num_mipmaps; + d3d11_srv_desc.Texture2DArray.ArraySize = (UINT)img->cmn.num_slices; + break; + default: + SOKOL_UNREACHABLE; break; + } + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex2d, &d3d11_srv_desc, &img->d3d11.srv); + if (!(SUCCEEDED(hr) && img->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_2D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.srv, desc->label); + } + SOKOL_ASSERT(img->d3d11.tex2d); + img->d3d11.res = (ID3D11Resource*)img->d3d11.tex2d; + _sg_d3d11_AddRef(img->d3d11.res); + } else { + // 3D texture - same procedure, first check if injected, than create non-injected + if (injected) { + img->d3d11.tex3d = (ID3D11Texture3D*) desc->d3d11_texture; + _sg_d3d11_AddRef(img->d3d11.tex3d); + img->d3d11.srv = (ID3D11ShaderResourceView*) desc->d3d11_shader_resource_view; + if (img->d3d11.srv) { + _sg_d3d11_AddRef(img->d3d11.srv); + } + } else { + // not injected, create 3d texture + D3D11_TEXTURE3D_DESC d3d11_tex_desc; + _sg_clear(&d3d11_tex_desc, sizeof(d3d11_tex_desc)); + d3d11_tex_desc.Width = (UINT)img->cmn.width; + d3d11_tex_desc.Height = (UINT)img->cmn.height; + d3d11_tex_desc.Depth = (UINT)img->cmn.num_slices; + d3d11_tex_desc.MipLevels = (UINT)img->cmn.num_mipmaps; + d3d11_tex_desc.Format = img->d3d11.format; + if (img->cmn.render_target) { + d3d11_tex_desc.Usage = D3D11_USAGE_DEFAULT; + d3d11_tex_desc.BindFlags = D3D11_BIND_RENDER_TARGET; + d3d11_tex_desc.CPUAccessFlags = 0; + } else { + d3d11_tex_desc.Usage = _sg_d3d11_usage(img->cmn.usage); + d3d11_tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + d3d11_tex_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(img->cmn.usage); + } + if (img->d3d11.format == DXGI_FORMAT_UNKNOWN) { + _SG_ERROR(D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT); + return SG_RESOURCESTATE_FAILED; + } + hr = _sg_d3d11_CreateTexture3D(_sg.d3d11.dev, &d3d11_tex_desc, init_data, &img->d3d11.tex3d); + if (!(SUCCEEDED(hr) && img->d3d11.tex3d)) { + _SG_ERROR(D3D11_CREATE_3D_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.tex3d, desc->label); + + // create shader-resource-view for 3D texture + if (!msaa) { + D3D11_SHADER_RESOURCE_VIEW_DESC d3d11_srv_desc; + _sg_clear(&d3d11_srv_desc, sizeof(d3d11_srv_desc)); + d3d11_srv_desc.Format = _sg_d3d11_srv_pixel_format(img->cmn.pixel_format); + d3d11_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + d3d11_srv_desc.Texture3D.MipLevels = (UINT)img->cmn.num_mipmaps; + hr = _sg_d3d11_CreateShaderResourceView(_sg.d3d11.dev, (ID3D11Resource*)img->d3d11.tex3d, &d3d11_srv_desc, &img->d3d11.srv); + if (!(SUCCEEDED(hr) && img->d3d11.srv)) { + _SG_ERROR(D3D11_CREATE_3D_SRV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(img->d3d11.srv, desc->label); + } + } + SOKOL_ASSERT(img->d3d11.tex3d); + img->d3d11.res = (ID3D11Resource*)img->d3d11.tex3d; + _sg_d3d11_AddRef(img->d3d11.res); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + if (img->d3d11.tex2d) { + _sg_d3d11_Release(img->d3d11.tex2d); + } + if (img->d3d11.tex3d) { + _sg_d3d11_Release(img->d3d11.tex3d); + } + if (img->d3d11.res) { + _sg_d3d11_Release(img->d3d11.res); + } + if (img->d3d11.srv) { + _sg_d3d11_Release(img->d3d11.srv); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + SOKOL_ASSERT(0 == smp->d3d11.smp); + const bool injected = (0 != desc->d3d11_sampler); + if (injected) { + smp->d3d11.smp = (ID3D11SamplerState*)desc->d3d11_sampler; + _sg_d3d11_AddRef(smp->d3d11.smp); + } else { + D3D11_SAMPLER_DESC d3d11_smp_desc; + _sg_clear(&d3d11_smp_desc, sizeof(d3d11_smp_desc)); + d3d11_smp_desc.Filter = _sg_d3d11_filter(desc->min_filter, desc->mag_filter, desc->mipmap_filter, desc->compare != SG_COMPAREFUNC_NEVER, desc->max_anisotropy); + d3d11_smp_desc.AddressU = _sg_d3d11_address_mode(desc->wrap_u); + d3d11_smp_desc.AddressV = _sg_d3d11_address_mode(desc->wrap_v); + d3d11_smp_desc.AddressW = _sg_d3d11_address_mode(desc->wrap_w); + d3d11_smp_desc.MipLODBias = 0.0f; // FIXME? + switch (desc->border_color) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: + // all 0.0f + break; + case SG_BORDERCOLOR_OPAQUE_WHITE: + for (int i = 0; i < 4; i++) { + d3d11_smp_desc.BorderColor[i] = 1.0f; + } + break; + default: + // opaque black + d3d11_smp_desc.BorderColor[3] = 1.0f; + break; + } + d3d11_smp_desc.MaxAnisotropy = desc->max_anisotropy; + d3d11_smp_desc.ComparisonFunc = _sg_d3d11_compare_func(desc->compare); + d3d11_smp_desc.MinLOD = desc->min_lod; + d3d11_smp_desc.MaxLOD = desc->max_lod; + HRESULT hr = _sg_d3d11_CreateSamplerState(_sg.d3d11.dev, &d3d11_smp_desc, &smp->d3d11.smp); + if (!(SUCCEEDED(hr) && smp->d3d11.smp)) { + _SG_ERROR(D3D11_CREATE_SAMPLER_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(smp->d3d11.smp, desc->label); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + if (smp->d3d11.smp) { + _sg_d3d11_Release(smp->d3d11.smp); + } +} + +_SOKOL_PRIVATE bool _sg_d3d11_load_d3dcompiler_dll(void) { + if ((0 == _sg.d3d11.d3dcompiler_dll) && !_sg.d3d11.d3dcompiler_dll_load_failed) { + _sg.d3d11.d3dcompiler_dll = LoadLibraryA("d3dcompiler_47.dll"); + if (0 == _sg.d3d11.d3dcompiler_dll) { + // don't attempt to load missing DLL in the future + _SG_ERROR(D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED); + _sg.d3d11.d3dcompiler_dll_load_failed = true; + return false; + } + // look up function pointers + _sg.d3d11.D3DCompile_func = (pD3DCompile)(void*) GetProcAddress(_sg.d3d11.d3dcompiler_dll, "D3DCompile"); + SOKOL_ASSERT(_sg.d3d11.D3DCompile_func); + } + return 0 != _sg.d3d11.d3dcompiler_dll; +} + +_SOKOL_PRIVATE ID3DBlob* _sg_d3d11_compile_shader(const sg_shader_function* shd_func) { + if (!_sg_d3d11_load_d3dcompiler_dll()) { + return NULL; + } + SOKOL_ASSERT(shd_func->d3d11_target); + UINT flags1 = D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR; + if (_sg.desc.d3d11_shader_debugging) { + flags1 |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + } else { + flags1 |= D3DCOMPILE_OPTIMIZATION_LEVEL3; + } + ID3DBlob* output = NULL; + ID3DBlob* errors_or_warnings = NULL; + HRESULT hr = _sg.d3d11.D3DCompile_func( + shd_func->source, // pSrcData + strlen(shd_func->source), // SrcDataSize + NULL, // pSourceName + NULL, // pDefines + NULL, // pInclude + shd_func->entry ? shd_func->entry : "main", // pEntryPoint + shd_func->d3d11_target, // pTarget + flags1, // Flags1 + 0, // Flags2 + &output, // ppCode + &errors_or_warnings); // ppErrorMsgs + if (FAILED(hr)) { + _SG_ERROR(D3D11_SHADER_COMPILATION_FAILED); + } + if (errors_or_warnings) { + _SG_WARN(D3D11_SHADER_COMPILATION_OUTPUT); + _SG_LOGMSG(D3D11_SHADER_COMPILATION_OUTPUT, (LPCSTR)_sg_d3d11_GetBufferPointer(errors_or_warnings)); + _sg_d3d11_Release(errors_or_warnings); errors_or_warnings = NULL; + } + if (FAILED(hr)) { + // just in case, usually output is NULL here + if (output) { + _sg_d3d11_Release(output); + output = NULL; + } + } + return output; +} + +// NOTE: this is an out-of-range check for HLSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_d3d11_ensure_hlsl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (desc->uniform_blocks[i].hlsl_register_b_n >= _SG_D3D11_MAX_STAGE_UB_BINDINGS) { + _SG_ERROR(D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE); + return false; + } + if (desc->storage_buffers[i].hlsl_register_u_n >= _SG_D3D11_MAX_STAGE_UAV_BINDINGS) { + _SG_ERROR(D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (desc->images[i].hlsl_register_t_n >= _SG_D3D11_MAX_STAGE_SRV_BINDINGS) { + _SG_ERROR(D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (desc->samplers[i].hlsl_register_s_n >= _SG_D3D11_MAX_STAGE_SMP_BINDINGS) { + _SG_ERROR(D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE); + return false; + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(!shd->d3d11.vs && !shd->d3d11.fs && !shd->d3d11.cs && !shd->d3d11.vs_blob); + HRESULT hr; + + // perform a range-check on HLSL bindslots that's also active in release + // mode to avoid potential out-of-bounds array accesses + if (!_sg_d3d11_ensure_hlsl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + // copy vertex attribute semantic names and indices + for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + _sg_strcpy(&shd->d3d11.attrs[i].sem_name, desc->attrs[i].hlsl_sem_name); + shd->d3d11.attrs[i].sem_index = desc->attrs[i].hlsl_sem_index; + } + + // copy HLSL bind slots + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + shd->d3d11.ub_register_b_n[i] = desc->uniform_blocks[i].hlsl_register_b_n; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + shd->d3d11.sbuf_register_t_n[i] = desc->storage_buffers[i].hlsl_register_t_n; + shd->d3d11.sbuf_register_u_n[i] = desc->storage_buffers[i].hlsl_register_u_n; + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + shd->d3d11.img_register_t_n[i] = desc->images[i].hlsl_register_t_n; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + shd->d3d11.smp_register_s_n[i] = desc->samplers[i].hlsl_register_s_n; + } + + // create a D3D constant buffer for each uniform block + for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) { + const sg_shader_stage stage = desc->uniform_blocks[ub_index].stage; + if (stage == SG_SHADERSTAGE_NONE) { + continue; + } + const _sg_shader_uniform_block_t* ub = &shd->cmn.uniform_blocks[ub_index]; + ID3D11Buffer* cbuf = 0; + D3D11_BUFFER_DESC cb_desc; + _sg_clear(&cb_desc, sizeof(cb_desc)); + cb_desc.ByteWidth = (UINT)_sg_roundup((int)ub->size, 16); + cb_desc.Usage = D3D11_USAGE_DEFAULT; + cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &cb_desc, NULL, &cbuf); + if (!(SUCCEEDED(hr) && cbuf)) { + _SG_ERROR(D3D11_CREATE_CONSTANT_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(cbuf, desc->label); + shd->d3d11.all_cbufs[ub_index] = cbuf; + + const uint8_t d3d11_slot = shd->d3d11.ub_register_b_n[ub_index]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_UB_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(0 == shd->d3d11.vs_cbufs[d3d11_slot]); + shd->d3d11.vs_cbufs[d3d11_slot] = cbuf; + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(0 == shd->d3d11.fs_cbufs[d3d11_slot]); + shd->d3d11.fs_cbufs[d3d11_slot] = cbuf; + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(0 == shd->d3d11.cs_cbufs[d3d11_slot]); + shd->d3d11.cs_cbufs[d3d11_slot] = cbuf; + } else { + SOKOL_UNREACHABLE; + } + } + + // create shader functions + const bool has_vs = desc->vertex_func.bytecode.ptr || desc->vertex_func.source; + const bool has_fs = desc->fragment_func.bytecode.ptr || desc->fragment_func.source; + const bool has_cs = desc->compute_func.bytecode.ptr || desc->compute_func.source; + bool vs_valid = false; bool fs_valid = false; bool cs_valid = false; + if (has_vs) { + const void* vs_ptr = 0; SIZE_T vs_length = 0; + ID3DBlob* vs_blob = 0; + if (desc->vertex_func.bytecode.ptr) { + SOKOL_ASSERT(desc->vertex_func.bytecode.size > 0); + vs_ptr = desc->vertex_func.bytecode.ptr; + vs_length = desc->vertex_func.bytecode.size; + } else { + SOKOL_ASSERT(desc->vertex_func.source); + vs_blob = _sg_d3d11_compile_shader(&desc->vertex_func); + if (vs_blob) { + vs_ptr = _sg_d3d11_GetBufferPointer(vs_blob); + vs_length = _sg_d3d11_GetBufferSize(vs_blob); + } + } + if (vs_ptr && (vs_length > 0)) { + hr = _sg_d3d11_CreateVertexShader(_sg.d3d11.dev, vs_ptr, vs_length, NULL, &shd->d3d11.vs); + vs_valid = SUCCEEDED(hr) && shd->d3d11.vs; + } + // set label, and need to store a copy of the vertex shader blob for the pipeline creation + if (vs_valid) { + _sg_d3d11_setlabel(shd->d3d11.vs, desc->label); + shd->d3d11.vs_blob_length = vs_length; + shd->d3d11.vs_blob = _sg_malloc((size_t)vs_length); + SOKOL_ASSERT(shd->d3d11.vs_blob); + memcpy(shd->d3d11.vs_blob, vs_ptr, vs_length); + } + if (vs_blob) { + _sg_d3d11_Release(vs_blob); + } + } + if (has_fs) { + const void* fs_ptr = 0; SIZE_T fs_length = 0; + ID3DBlob* fs_blob = 0; + if (desc->fragment_func.bytecode.ptr) { + SOKOL_ASSERT(desc->fragment_func.bytecode.size > 0); + fs_ptr = desc->fragment_func.bytecode.ptr; + fs_length = desc->fragment_func.bytecode.size; + } else { + SOKOL_ASSERT(desc->fragment_func.source); + fs_blob = _sg_d3d11_compile_shader(&desc->fragment_func); + if (fs_blob) { + fs_ptr = _sg_d3d11_GetBufferPointer(fs_blob); + fs_length = _sg_d3d11_GetBufferSize(fs_blob); + } + } + if (fs_ptr && (fs_length > 0)) { + hr = _sg_d3d11_CreatePixelShader(_sg.d3d11.dev, fs_ptr, fs_length, NULL, &shd->d3d11.fs); + fs_valid = SUCCEEDED(hr) && shd->d3d11.fs; + } + if (fs_valid) { + _sg_d3d11_setlabel(shd->d3d11.fs, desc->label); + } + if (fs_blob) { + _sg_d3d11_Release(fs_blob); + } + } + if (has_cs) { + const void* cs_ptr = 0; SIZE_T cs_length = 0; + ID3DBlob* cs_blob = 0; + if (desc->compute_func.bytecode.ptr) { + SOKOL_ASSERT(desc->compute_func.bytecode.size > 0); + cs_ptr = desc->compute_func.bytecode.ptr; + cs_length = desc->compute_func.bytecode.size; + } else { + SOKOL_ASSERT(desc->compute_func.source); + cs_blob = _sg_d3d11_compile_shader(&desc->compute_func); + if (cs_blob) { + cs_ptr = _sg_d3d11_GetBufferPointer(cs_blob); + cs_length = _sg_d3d11_GetBufferSize(cs_blob); + } + } + if (cs_ptr && (cs_length > 0)) { + hr = _sg_d3d11_CreateComputeShader(_sg.d3d11.dev, cs_ptr, cs_length, NULL, &shd->d3d11.cs); + cs_valid = SUCCEEDED(hr) && shd->d3d11.cs; + } + if (cs_blob) { + _sg_d3d11_Release(cs_blob); + } + } + if ((vs_valid && fs_valid) || cs_valid) { + return SG_RESOURCESTATE_VALID; + } else { + return SG_RESOURCESTATE_FAILED; + } +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + if (shd->d3d11.vs) { + _sg_d3d11_Release(shd->d3d11.vs); + } + if (shd->d3d11.fs) { + _sg_d3d11_Release(shd->d3d11.fs); + } + if (shd->d3d11.cs) { + _sg_d3d11_Release(shd->d3d11.cs); + } + if (shd->d3d11.vs_blob) { + _sg_free(shd->d3d11.vs_blob); + } + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (shd->d3d11.all_cbufs[i]) { + _sg_d3d11_Release(shd->d3d11.all_cbufs[i]); + } + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && shd && desc); + SOKOL_ASSERT(desc->shader.id == shd->slot.id); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_VALID); + + pip->shader = shd; + + // if this is a compute pipeline, we're done here + if (pip->cmn.is_compute) { + return SG_RESOURCESTATE_VALID; + } + + // a render pipeline... + SOKOL_ASSERT(shd->d3d11.vs_blob && shd->d3d11.vs_blob_length > 0); + SOKOL_ASSERT(!pip->d3d11.il && !pip->d3d11.rs && !pip->d3d11.dss && !pip->d3d11.bs); + + pip->d3d11.index_format = _sg_d3d11_index_format(pip->cmn.index_type); + pip->d3d11.topology = _sg_d3d11_primitive_topology(desc->primitive_type); + pip->d3d11.stencil_ref = desc->stencil.ref; + + // create input layout object + HRESULT hr; + D3D11_INPUT_ELEMENT_DESC d3d11_comps[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_clear(d3d11_comps, sizeof(d3d11_comps)); + size_t attr_index = 0; + for (; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[a_state->buffer_index]); + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[a_state->buffer_index]; + const sg_vertex_step step_func = l_state->step_func; + const int step_rate = l_state->step_rate; + D3D11_INPUT_ELEMENT_DESC* d3d11_comp = &d3d11_comps[attr_index]; + d3d11_comp->SemanticName = _sg_strptr(&shd->d3d11.attrs[attr_index].sem_name); + d3d11_comp->SemanticIndex = (UINT)shd->d3d11.attrs[attr_index].sem_index; + d3d11_comp->Format = _sg_d3d11_vertex_format(a_state->format); + d3d11_comp->InputSlot = (UINT)a_state->buffer_index; + d3d11_comp->AlignedByteOffset = (UINT)a_state->offset; + d3d11_comp->InputSlotClass = _sg_d3d11_input_classification(step_func); + if (SG_VERTEXSTEP_PER_INSTANCE == step_func) { + d3d11_comp->InstanceDataStepRate = (UINT)step_rate; + pip->cmn.use_instanced_draw = true; + } + } + for (size_t layout_index = 0; layout_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; layout_index++) { + if (pip->cmn.vertex_buffer_layout_active[layout_index]) { + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[layout_index]; + SOKOL_ASSERT(l_state->stride > 0); + pip->d3d11.vb_strides[layout_index] = (UINT)l_state->stride; + } else { + pip->d3d11.vb_strides[layout_index] = 0; + } + } + if (attr_index > 0) { + hr = _sg_d3d11_CreateInputLayout(_sg.d3d11.dev, + d3d11_comps, // pInputElementDesc + (UINT)attr_index, // NumElements + shd->d3d11.vs_blob, // pShaderByteCodeWithInputSignature + shd->d3d11.vs_blob_length, // BytecodeLength + &pip->d3d11.il); + if (!(SUCCEEDED(hr) && pip->d3d11.il)) { + _SG_ERROR(D3D11_CREATE_INPUT_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.il, desc->label); + } + + // create rasterizer state + D3D11_RASTERIZER_DESC rs_desc; + _sg_clear(&rs_desc, sizeof(rs_desc)); + rs_desc.FillMode = D3D11_FILL_SOLID; + rs_desc.CullMode = _sg_d3d11_cull_mode(desc->cull_mode); + rs_desc.FrontCounterClockwise = desc->face_winding == SG_FACEWINDING_CCW; + rs_desc.DepthBias = (INT) pip->cmn.depth.bias; + rs_desc.DepthBiasClamp = pip->cmn.depth.bias_clamp; + rs_desc.SlopeScaledDepthBias = pip->cmn.depth.bias_slope_scale; + rs_desc.DepthClipEnable = TRUE; + rs_desc.ScissorEnable = TRUE; + rs_desc.MultisampleEnable = desc->sample_count > 1; + rs_desc.AntialiasedLineEnable = FALSE; + hr = _sg_d3d11_CreateRasterizerState(_sg.d3d11.dev, &rs_desc, &pip->d3d11.rs); + if (!(SUCCEEDED(hr) && pip->d3d11.rs)) { + _SG_ERROR(D3D11_CREATE_RASTERIZER_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.rs, desc->label); + + // create depth-stencil state + D3D11_DEPTH_STENCIL_DESC dss_desc; + _sg_clear(&dss_desc, sizeof(dss_desc)); + dss_desc.DepthEnable = TRUE; + dss_desc.DepthWriteMask = desc->depth.write_enabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO; + dss_desc.DepthFunc = _sg_d3d11_compare_func(desc->depth.compare); + dss_desc.StencilEnable = desc->stencil.enabled; + dss_desc.StencilReadMask = desc->stencil.read_mask; + dss_desc.StencilWriteMask = desc->stencil.write_mask; + const sg_stencil_face_state* sf = &desc->stencil.front; + dss_desc.FrontFace.StencilFailOp = _sg_d3d11_stencil_op(sf->fail_op); + dss_desc.FrontFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sf->depth_fail_op); + dss_desc.FrontFace.StencilPassOp = _sg_d3d11_stencil_op(sf->pass_op); + dss_desc.FrontFace.StencilFunc = _sg_d3d11_compare_func(sf->compare); + const sg_stencil_face_state* sb = &desc->stencil.back; + dss_desc.BackFace.StencilFailOp = _sg_d3d11_stencil_op(sb->fail_op); + dss_desc.BackFace.StencilDepthFailOp = _sg_d3d11_stencil_op(sb->depth_fail_op); + dss_desc.BackFace.StencilPassOp = _sg_d3d11_stencil_op(sb->pass_op); + dss_desc.BackFace.StencilFunc = _sg_d3d11_compare_func(sb->compare); + hr = _sg_d3d11_CreateDepthStencilState(_sg.d3d11.dev, &dss_desc, &pip->d3d11.dss); + if (!(SUCCEEDED(hr) && pip->d3d11.dss)) { + _SG_ERROR(D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.dss, desc->label); + + // create blend state + D3D11_BLEND_DESC bs_desc; + _sg_clear(&bs_desc, sizeof(bs_desc)); + bs_desc.AlphaToCoverageEnable = desc->alpha_to_coverage_enabled; + bs_desc.IndependentBlendEnable = TRUE; + { + size_t i = 0; + for (i = 0; i < (size_t)desc->color_count; i++) { + const sg_blend_state* src = &desc->colors[i].blend; + D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i]; + dst->BlendEnable = src->enabled; + dst->SrcBlend = _sg_d3d11_blend_factor(src->src_factor_rgb); + dst->DestBlend = _sg_d3d11_blend_factor(src->dst_factor_rgb); + dst->BlendOp = _sg_d3d11_blend_op(src->op_rgb); + dst->SrcBlendAlpha = _sg_d3d11_blend_factor(src->src_factor_alpha); + dst->DestBlendAlpha = _sg_d3d11_blend_factor(src->dst_factor_alpha); + dst->BlendOpAlpha = _sg_d3d11_blend_op(src->op_alpha); + dst->RenderTargetWriteMask = _sg_d3d11_color_write_mask(desc->colors[i].write_mask); + } + for (; i < 8; i++) { + D3D11_RENDER_TARGET_BLEND_DESC* dst = &bs_desc.RenderTarget[i]; + dst->BlendEnable = FALSE; + dst->SrcBlend = dst->SrcBlendAlpha = D3D11_BLEND_ONE; + dst->DestBlend = dst->DestBlendAlpha = D3D11_BLEND_ZERO; + dst->BlendOp = dst->BlendOpAlpha = D3D11_BLEND_OP_ADD; + dst->RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + } + } + hr = _sg_d3d11_CreateBlendState(_sg.d3d11.dev, &bs_desc, &pip->d3d11.bs); + if (!(SUCCEEDED(hr) && pip->d3d11.bs)) { + _SG_ERROR(D3D11_CREATE_BLEND_STATE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(pip->d3d11.bs, desc->label); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + if (pip == _sg.d3d11.cur_pipeline) { + _sg.d3d11.cur_pipeline = 0; + _sg.d3d11.cur_pipeline_id.id = SG_INVALID_ID; + } + if (pip->d3d11.il) { + _sg_d3d11_Release(pip->d3d11.il); + } + if (pip->d3d11.rs) { + _sg_d3d11_Release(pip->d3d11.rs); + } + if (pip->d3d11.dss) { + _sg_d3d11_Release(pip->d3d11.dss); + } + if (pip->d3d11.bs) { + _sg_d3d11_Release(pip->d3d11.bs); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_d3d11_create_attachments(_sg_attachments_t* atts, _sg_image_t** color_images, _sg_image_t** resolve_images, _sg_image_t* ds_img, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + SOKOL_ASSERT(color_images && resolve_images); + SOKOL_ASSERT(_sg.d3d11.dev); + + // copy image pointers + for (size_t i = 0; i < (size_t)atts->cmn.num_colors; i++) { + const sg_attachment_desc* color_desc = &desc->colors[i]; + _SOKOL_UNUSED(color_desc); + SOKOL_ASSERT(color_desc->image.id != SG_INVALID_ID); + SOKOL_ASSERT(0 == atts->d3d11.colors[i].image); + SOKOL_ASSERT(color_images[i] && (color_images[i]->slot.id == color_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(color_images[i]->cmn.pixel_format)); + atts->d3d11.colors[i].image = color_images[i]; + + const sg_attachment_desc* resolve_desc = &desc->resolves[i]; + if (resolve_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(0 == atts->d3d11.resolves[i].image); + SOKOL_ASSERT(resolve_images[i] && (resolve_images[i]->slot.id == resolve_desc->image.id)); + SOKOL_ASSERT(color_images[i] && (color_images[i]->cmn.pixel_format == resolve_images[i]->cmn.pixel_format)); + atts->d3d11.resolves[i].image = resolve_images[i]; + } + } + SOKOL_ASSERT(0 == atts->d3d11.depth_stencil.image); + const sg_attachment_desc* ds_desc = &desc->depth_stencil; + if (ds_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(ds_img && (ds_img->slot.id == ds_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(ds_img->cmn.pixel_format)); + atts->d3d11.depth_stencil.image = ds_img; + } + + // create render-target views + for (size_t i = 0; i < (size_t)atts->cmn.num_colors; i++) { + const _sg_attachment_common_t* cmn_color_att = &atts->cmn.colors[i]; + const _sg_image_t* color_img = color_images[i]; + SOKOL_ASSERT(0 == atts->d3d11.colors[i].view.rtv); + const bool msaa = color_img->cmn.sample_count > 1; + D3D11_RENDER_TARGET_VIEW_DESC d3d11_rtv_desc; + _sg_clear(&d3d11_rtv_desc, sizeof(d3d11_rtv_desc)); + d3d11_rtv_desc.Format = _sg_d3d11_rtv_pixel_format(color_img->cmn.pixel_format); + if (color_img->cmn.type == SG_IMAGETYPE_2D) { + if (msaa) { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS; + } else { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + d3d11_rtv_desc.Texture2D.MipSlice = (UINT)cmn_color_att->mip_level; + } + } else if ((color_img->cmn.type == SG_IMAGETYPE_CUBE) || (color_img->cmn.type == SG_IMAGETYPE_ARRAY)) { + if (msaa) { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY; + d3d11_rtv_desc.Texture2DMSArray.FirstArraySlice = (UINT)cmn_color_att->slice; + d3d11_rtv_desc.Texture2DMSArray.ArraySize = 1; + } else { + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; + d3d11_rtv_desc.Texture2DArray.MipSlice = (UINT)cmn_color_att->mip_level; + d3d11_rtv_desc.Texture2DArray.FirstArraySlice = (UINT)cmn_color_att->slice; + d3d11_rtv_desc.Texture2DArray.ArraySize = 1; + } + } else { + SOKOL_ASSERT(color_img->cmn.type == SG_IMAGETYPE_3D); + SOKOL_ASSERT(!msaa); + d3d11_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D; + d3d11_rtv_desc.Texture3D.MipSlice = (UINT)cmn_color_att->mip_level; + d3d11_rtv_desc.Texture3D.FirstWSlice = (UINT)cmn_color_att->slice; + d3d11_rtv_desc.Texture3D.WSize = 1; + } + SOKOL_ASSERT(color_img->d3d11.res); + HRESULT hr = _sg_d3d11_CreateRenderTargetView(_sg.d3d11.dev, color_img->d3d11.res, &d3d11_rtv_desc, &atts->d3d11.colors[i].view.rtv); + if (!(SUCCEEDED(hr) && atts->d3d11.colors[i].view.rtv)) { + _SG_ERROR(D3D11_CREATE_RTV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(atts->d3d11.colors[i].view.rtv, desc->label); + } + SOKOL_ASSERT(0 == atts->d3d11.depth_stencil.view.dsv); + if (ds_desc->image.id != SG_INVALID_ID) { + const _sg_attachment_common_t* cmn_ds_att = &atts->cmn.depth_stencil; + const bool msaa = ds_img->cmn.sample_count > 1; + D3D11_DEPTH_STENCIL_VIEW_DESC d3d11_dsv_desc; + _sg_clear(&d3d11_dsv_desc, sizeof(d3d11_dsv_desc)); + d3d11_dsv_desc.Format = _sg_d3d11_dsv_pixel_format(ds_img->cmn.pixel_format); + SOKOL_ASSERT(ds_img && ds_img->cmn.type != SG_IMAGETYPE_3D); + if (ds_img->cmn.type == SG_IMAGETYPE_2D) { + if (msaa) { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS; + } else { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + d3d11_dsv_desc.Texture2D.MipSlice = (UINT)cmn_ds_att->mip_level; + } + } else if ((ds_img->cmn.type == SG_IMAGETYPE_CUBE) || (ds_img->cmn.type == SG_IMAGETYPE_ARRAY)) { + if (msaa) { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY; + d3d11_dsv_desc.Texture2DMSArray.FirstArraySlice = (UINT)cmn_ds_att->slice; + d3d11_dsv_desc.Texture2DMSArray.ArraySize = 1; + } else { + d3d11_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + d3d11_dsv_desc.Texture2DArray.MipSlice = (UINT)cmn_ds_att->mip_level; + d3d11_dsv_desc.Texture2DArray.FirstArraySlice = (UINT)cmn_ds_att->slice; + d3d11_dsv_desc.Texture2DArray.ArraySize = 1; + } + } + SOKOL_ASSERT(ds_img->d3d11.res); + HRESULT hr = _sg_d3d11_CreateDepthStencilView(_sg.d3d11.dev, ds_img->d3d11.res, &d3d11_dsv_desc, &atts->d3d11.depth_stencil.view.dsv); + if (!(SUCCEEDED(hr) && atts->d3d11.depth_stencil.view.dsv)) { + _SG_ERROR(D3D11_CREATE_DSV_FAILED); + return SG_RESOURCESTATE_FAILED; + } + _sg_d3d11_setlabel(atts->d3d11.depth_stencil.view.dsv, desc->label); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts->d3d11.colors[i].view.rtv) { + _sg_d3d11_Release(atts->d3d11.colors[i].view.rtv); + } + if (atts->d3d11.resolves[i].view.rtv) { + _sg_d3d11_Release(atts->d3d11.resolves[i].view.rtv); + } + } + if (atts->d3d11.depth_stencil.view.dsv) { + _sg_d3d11_Release(atts->d3d11.depth_stencil.view.dsv); + } +} + +_SOKOL_PRIVATE _sg_image_t* _sg_d3d11_attachments_color_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->d3d11.colors[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_d3d11_attachments_resolve_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->d3d11.resolves[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_d3d11_attachments_ds_image(const _sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + return atts->d3d11.depth_stencil.image; +} + +_SOKOL_PRIVATE void _sg_d3d11_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + if (_sg.cur_pass.is_compute) { + // nothing to do in compute passes + return; + } + const _sg_attachments_t* atts = _sg.cur_pass.atts; + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + int num_rtvs = 0; + ID3D11RenderTargetView* rtvs[SG_MAX_COLOR_ATTACHMENTS] = { 0 }; + ID3D11DepthStencilView* dsv = 0; + _sg.d3d11.cur_pass.render_view = 0; + _sg.d3d11.cur_pass.resolve_view = 0; + if (atts) { + num_rtvs = atts->cmn.num_colors; + for (size_t i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + rtvs[i] = atts->d3d11.colors[i].view.rtv; + } + dsv = atts->d3d11.depth_stencil.view.dsv; + } else { + // NOTE: depth-stencil-view is optional + SOKOL_ASSERT(swapchain->d3d11.render_view); + num_rtvs = 1; + rtvs[0] = (ID3D11RenderTargetView*) swapchain->d3d11.render_view; + dsv = (ID3D11DepthStencilView*) swapchain->d3d11.depth_stencil_view; + _sg.d3d11.cur_pass.render_view = (ID3D11RenderTargetView*) swapchain->d3d11.render_view; + _sg.d3d11.cur_pass.resolve_view = (ID3D11RenderTargetView*) swapchain->d3d11.resolve_view; + } + // apply the render-target- and depth-stencil-views + _sg_d3d11_OMSetRenderTargets(_sg.d3d11.ctx, SG_MAX_COLOR_ATTACHMENTS, rtvs, dsv); + _sg_stats_add(d3d11.pass.num_om_set_render_targets, 1); + + // set viewport and scissor rect to cover whole screen + D3D11_VIEWPORT vp; + _sg_clear(&vp, sizeof(vp)); + vp.Width = (FLOAT) _sg.cur_pass.width; + vp.Height = (FLOAT) _sg.cur_pass.height; + vp.MaxDepth = 1.0f; + _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp); + D3D11_RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = _sg.cur_pass.width; + rect.bottom = _sg.cur_pass.height; + _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); + + // perform clear action + for (size_t i = 0; i < (size_t)num_rtvs; i++) { + if (action->colors[i].load_action == SG_LOADACTION_CLEAR) { + _sg_d3d11_ClearRenderTargetView(_sg.d3d11.ctx, rtvs[i], (float*)&action->colors[i].clear_value); + _sg_stats_add(d3d11.pass.num_clear_render_target_view, 1); + } + } + UINT ds_flags = 0; + if (action->depth.load_action == SG_LOADACTION_CLEAR) { + ds_flags |= D3D11_CLEAR_DEPTH; + } + if (action->stencil.load_action == SG_LOADACTION_CLEAR) { + ds_flags |= D3D11_CLEAR_STENCIL; + } + if ((0 != ds_flags) && dsv) { + _sg_d3d11_ClearDepthStencilView(_sg.d3d11.ctx, dsv, ds_flags, action->depth.clear_value, action->stencil.clear_value); + _sg_stats_add(d3d11.pass.num_clear_depth_stencil_view, 1); + } +} + +// D3D11CalcSubresource only exists for C++ +_SOKOL_PRIVATE UINT _sg_d3d11_calcsubresource(UINT mip_slice, UINT array_slice, UINT mip_levels) { + return mip_slice + array_slice * mip_levels; +} + +_SOKOL_PRIVATE void _sg_d3d11_end_pass(void) { + SOKOL_ASSERT(_sg.d3d11.ctx); + + if (!_sg.cur_pass.is_compute) { + // need to resolve MSAA render attachments into texture? + if (_sg.cur_pass.atts_id.id != SG_INVALID_ID) { + // ...for offscreen pass... + SOKOL_ASSERT(_sg.cur_pass.atts && _sg.cur_pass.atts->slot.id == _sg.cur_pass.atts_id.id); + for (size_t i = 0; i < (size_t)_sg.cur_pass.atts->cmn.num_colors; i++) { + const _sg_image_t* resolve_img = _sg.cur_pass.atts->d3d11.resolves[i].image; + if (resolve_img) { + const _sg_image_t* color_img = _sg.cur_pass.atts->d3d11.colors[i].image; + const _sg_attachment_common_t* cmn_color_att = &_sg.cur_pass.atts->cmn.colors[i]; + const _sg_attachment_common_t* cmn_resolve_att = &_sg.cur_pass.atts->cmn.resolves[i]; + SOKOL_ASSERT(resolve_img->slot.id == cmn_resolve_att->image_id.id); + SOKOL_ASSERT(color_img && (color_img->slot.id == cmn_color_att->image_id.id)); + SOKOL_ASSERT(color_img->cmn.sample_count > 1); + SOKOL_ASSERT(resolve_img->cmn.sample_count == 1); + const UINT src_subres = _sg_d3d11_calcsubresource( + (UINT)cmn_color_att->mip_level, + (UINT)cmn_color_att->slice, + (UINT)color_img->cmn.num_mipmaps); + const UINT dst_subres = _sg_d3d11_calcsubresource( + (UINT)cmn_resolve_att->mip_level, + (UINT)cmn_resolve_att->slice, + (UINT)resolve_img->cmn.num_mipmaps); + _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx, + resolve_img->d3d11.res, + dst_subres, + color_img->d3d11.res, + src_subres, + color_img->d3d11.format); + _sg_stats_add(d3d11.pass.num_resolve_subresource, 1); + } + } + } else { + // ...for swapchain pass... + if (_sg.d3d11.cur_pass.resolve_view) { + SOKOL_ASSERT(_sg.d3d11.cur_pass.render_view); + SOKOL_ASSERT(_sg.cur_pass.swapchain.sample_count > 1); + SOKOL_ASSERT(_sg.cur_pass.swapchain.color_fmt > SG_PIXELFORMAT_NONE); + ID3D11Resource* d3d11_render_res = 0; + ID3D11Resource* d3d11_resolve_res = 0; + _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_pass.render_view, &d3d11_render_res); + _sg_d3d11_GetResource((ID3D11View*)_sg.d3d11.cur_pass.resolve_view, &d3d11_resolve_res); + SOKOL_ASSERT(d3d11_render_res); + SOKOL_ASSERT(d3d11_resolve_res); + const sg_pixel_format color_fmt = _sg.cur_pass.swapchain.color_fmt; + _sg_d3d11_ResolveSubresource(_sg.d3d11.ctx, d3d11_resolve_res, 0, d3d11_render_res, 0, _sg_d3d11_rtv_pixel_format(color_fmt)); + _sg_d3d11_Release(d3d11_render_res); + _sg_d3d11_Release(d3d11_resolve_res); + _sg_stats_add(d3d11.pass.num_resolve_subresource, 1); + } + } + } + _sg.d3d11.cur_pass.render_view = 0; + _sg.d3d11.cur_pass.resolve_view = 0; + _sg.d3d11.cur_pipeline = 0; + _sg.d3d11.cur_pipeline_id.id = SG_INVALID_ID; + _sg_d3d11_clear_state(); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.d3d11.ctx); + D3D11_VIEWPORT vp; + vp.TopLeftX = (FLOAT) x; + vp.TopLeftY = (FLOAT) (origin_top_left ? y : (_sg.cur_pass.height - (y + h))); + vp.Width = (FLOAT) w; + vp.Height = (FLOAT) h; + vp.MinDepth = 0.0f; + vp.MaxDepth = 1.0f; + _sg_d3d11_RSSetViewports(_sg.d3d11.ctx, 1, &vp); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.d3d11.ctx); + D3D11_RECT rect; + rect.left = x; + rect.top = (origin_top_left ? y : (_sg.cur_pass.height - (y + h))); + rect.right = x + w; + rect.bottom = origin_top_left ? (y + h) : (_sg.cur_pass.height - y); + _sg_d3d11_RSSetScissorRects(_sg.d3d11.ctx, 1, &rect); +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + SOKOL_ASSERT(_sg.d3d11.ctx); + + _sg.d3d11.cur_pipeline = pip; + _sg.d3d11.cur_pipeline_id.id = pip->slot.id; + + if (pip->cmn.is_compute) { + // a compute pipeline + SOKOL_ASSERT(pip->shader->d3d11.cs); + _sg_d3d11_CSSetShader(_sg.d3d11.ctx, pip->shader->d3d11.cs, NULL, 0); + _sg_d3d11_CSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, pip->shader->d3d11.cs_cbufs); + _sg_stats_add(d3d11.pipeline.num_cs_set_shader, 1); + _sg_stats_add(d3d11.pipeline.num_cs_set_constant_buffers, 1); + } else { + // a render pipeline + SOKOL_ASSERT(pip->d3d11.rs && pip->d3d11.bs && pip->d3d11.dss); + SOKOL_ASSERT(pip->shader->d3d11.vs); + SOKOL_ASSERT(pip->shader->d3d11.fs); + + _sg.d3d11.use_indexed_draw = (pip->d3d11.index_format != DXGI_FORMAT_UNKNOWN); + _sg.d3d11.use_instanced_draw = pip->cmn.use_instanced_draw; + + _sg_d3d11_RSSetState(_sg.d3d11.ctx, pip->d3d11.rs); + _sg_d3d11_OMSetDepthStencilState(_sg.d3d11.ctx, pip->d3d11.dss, pip->d3d11.stencil_ref); + _sg_d3d11_OMSetBlendState(_sg.d3d11.ctx, pip->d3d11.bs, (float*)&pip->cmn.blend_color, 0xFFFFFFFF); + _sg_d3d11_IASetPrimitiveTopology(_sg.d3d11.ctx, pip->d3d11.topology); + _sg_d3d11_IASetInputLayout(_sg.d3d11.ctx, pip->d3d11.il); + _sg_d3d11_VSSetShader(_sg.d3d11.ctx, pip->shader->d3d11.vs, NULL, 0); + _sg_d3d11_VSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, pip->shader->d3d11.vs_cbufs); + _sg_d3d11_PSSetShader(_sg.d3d11.ctx, pip->shader->d3d11.fs, NULL, 0); + _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UB_BINDINGS, pip->shader->d3d11.fs_cbufs); + _sg_stats_add(d3d11.pipeline.num_rs_set_state, 1); + _sg_stats_add(d3d11.pipeline.num_om_set_depth_stencil_state, 1); + _sg_stats_add(d3d11.pipeline.num_om_set_blend_state, 1); + _sg_stats_add(d3d11.pipeline.num_ia_set_primitive_topology, 1); + _sg_stats_add(d3d11.pipeline.num_ia_set_input_layout, 1); + _sg_stats_add(d3d11.pipeline.num_vs_set_shader, 1); + _sg_stats_add(d3d11.pipeline.num_vs_set_constant_buffers, 1); + _sg_stats_add(d3d11.pipeline.num_ps_set_shader, 1); + _sg_stats_add(d3d11.pipeline.num_ps_set_constant_buffers, 1); + } +} + +_SOKOL_PRIVATE bool _sg_d3d11_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip && bnd->pip->shader); + SOKOL_ASSERT(bnd->pip->shader->slot.id == bnd->pip->cmn.shader_id.id); + SOKOL_ASSERT(_sg.d3d11.ctx); + const _sg_shader_t* shd = bnd->pip->shader; + const bool is_compute = bnd->pip->cmn.is_compute; + + // gather all the D3D11 resources into arrays + ID3D11Buffer* d3d11_ib = bnd->ib ? bnd->ib->d3d11.buf : 0; + ID3D11Buffer* d3d11_vbs[SG_MAX_VERTEXBUFFER_BINDSLOTS] = {0}; + UINT d3d11_vb_offsets[SG_MAX_VERTEXBUFFER_BINDSLOTS] = {0}; + ID3D11ShaderResourceView* d3d11_vs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS] = {0}; + ID3D11ShaderResourceView* d3d11_fs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS] = {0}; + ID3D11ShaderResourceView* d3d11_cs_srvs[_SG_D3D11_MAX_STAGE_SRV_BINDINGS] = {0}; + ID3D11SamplerState* d3d11_vs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS] = {0}; + ID3D11SamplerState* d3d11_fs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS] = {0}; + ID3D11SamplerState* d3d11_cs_smps[_SG_D3D11_MAX_STAGE_SMP_BINDINGS] = {0}; + ID3D11UnorderedAccessView* d3d11_cs_uavs[_SG_D3D11_MAX_STAGE_UAV_BINDINGS] = {0}; + + if (!is_compute) { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* vb = bnd->vbs[i]; + if (vb == 0) { + continue; + } + SOKOL_ASSERT(vb->d3d11.buf); + d3d11_vbs[i] = vb->d3d11.buf; + d3d11_vb_offsets[i] = (UINT)bnd->vb_offsets[i]; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + const _sg_image_t* img = bnd->imgs[i]; + if (img == 0) { + continue; + } + const sg_shader_stage stage = shd->cmn.images[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE); + const uint8_t d3d11_slot = shd->d3d11.img_register_t_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS); + SOKOL_ASSERT(img->d3d11.srv); + ID3D11ShaderResourceView* d3d11_srv = img->d3d11.srv; + switch (stage) { + case SG_SHADERSTAGE_VERTEX: d3d11_vs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_FRAGMENT: d3d11_fs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_COMPUTE: d3d11_cs_srvs[d3d11_slot] = d3d11_srv; break; + default: SOKOL_UNREACHABLE; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* sbuf = bnd->sbufs[i]; + if (sbuf == 0) { + continue; + } + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE); + if (shd->cmn.storage_buffers[i].readonly) { + SOKOL_ASSERT(sbuf->d3d11.srv); + const uint8_t d3d11_slot = shd->d3d11.sbuf_register_t_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SRV_BINDINGS); + ID3D11ShaderResourceView* d3d11_srv = sbuf->d3d11.srv; + switch (stage) { + case SG_SHADERSTAGE_VERTEX: d3d11_vs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_FRAGMENT: d3d11_fs_srvs[d3d11_slot] = d3d11_srv; break; + case SG_SHADERSTAGE_COMPUTE: d3d11_cs_srvs[d3d11_slot] = d3d11_srv; break; + default: SOKOL_UNREACHABLE; + } + } else { + SOKOL_ASSERT(sbuf->d3d11.uav); + SOKOL_ASSERT(stage == SG_SHADERSTAGE_COMPUTE); + const uint8_t d3d11_slot = shd->d3d11.sbuf_register_u_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_UAV_BINDINGS); + d3d11_cs_uavs[d3d11_slot] = sbuf->d3d11.uav; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const _sg_sampler_t* smp = bnd->smps[i]; + if (smp == 0) { + continue; + } + const sg_shader_stage stage = shd->cmn.samplers[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_NONE); + const uint8_t d3d11_slot = shd->d3d11.smp_register_s_n[i]; + SOKOL_ASSERT(d3d11_slot < _SG_D3D11_MAX_STAGE_SMP_BINDINGS); + SOKOL_ASSERT(smp->d3d11.smp); + ID3D11SamplerState* d3d11_smp = smp->d3d11.smp; + switch (stage) { + case SG_SHADERSTAGE_VERTEX: d3d11_vs_smps[d3d11_slot] = d3d11_smp; break; + case SG_SHADERSTAGE_FRAGMENT: d3d11_fs_smps[d3d11_slot] = d3d11_smp; break; + case SG_SHADERSTAGE_COMPUTE: d3d11_cs_smps[d3d11_slot] = d3d11_smp; break; + default: SOKOL_UNREACHABLE; + } + } + if (is_compute) { + _sg_d3d11_CSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, d3d11_cs_srvs); + _sg_d3d11_CSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, d3d11_cs_smps); + _sg_d3d11_CSSetUnorderedAccessViews(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_UAV_BINDINGS, d3d11_cs_uavs, NULL); + _sg_stats_add(d3d11.bindings.num_cs_set_shader_resources, 1); + _sg_stats_add(d3d11.bindings.num_cs_set_samplers, 1); + _sg_stats_add(d3d11.bindings.num_cs_set_unordered_access_views, 1); + } else { + _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_VERTEXBUFFER_BINDSLOTS, d3d11_vbs, bnd->pip->d3d11.vb_strides, d3d11_vb_offsets); + _sg_d3d11_IASetIndexBuffer(_sg.d3d11.ctx, d3d11_ib, bnd->pip->d3d11.index_format, (UINT)bnd->ib_offset); + _sg_d3d11_VSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, d3d11_vs_srvs); + _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SRV_BINDINGS, d3d11_fs_srvs); + _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, d3d11_vs_smps); + _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, _SG_D3D11_MAX_STAGE_SMP_BINDINGS, d3d11_fs_smps); + _sg_stats_add(d3d11.bindings.num_ia_set_vertex_buffers, 1); + _sg_stats_add(d3d11.bindings.num_ia_set_index_buffer, 1); + _sg_stats_add(d3d11.bindings.num_vs_set_shader_resources, 1); + _sg_stats_add(d3d11.bindings.num_ps_set_shader_resources, 1); + _sg_stats_add(d3d11.bindings.num_vs_set_samplers, 1); + _sg_stats_add(d3d11.bindings.num_ps_set_samplers, 1); + } + return true; +} + +_SOKOL_PRIVATE void _sg_d3d11_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT(_sg.d3d11.cur_pipeline && _sg.d3d11.cur_pipeline->slot.id == _sg.d3d11.cur_pipeline_id.id); + const _sg_shader_t* shd = _sg.d3d11.cur_pipeline->shader; + SOKOL_ASSERT(shd && (shd->slot.id == _sg.d3d11.cur_pipeline->cmn.shader_id.id)); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + + ID3D11Buffer* cbuf = shd->d3d11.all_cbufs[ub_slot]; + SOKOL_ASSERT(cbuf); + _sg_d3d11_UpdateSubresource(_sg.d3d11.ctx, (ID3D11Resource*)cbuf, 0, NULL, data->ptr, 0, 0); + _sg_stats_add(d3d11.uniforms.num_update_subresource, 1); +} + +_SOKOL_PRIVATE void _sg_d3d11_draw(int base_element, int num_elements, int num_instances) { + const bool use_instanced_draw = (num_instances > 1) || (_sg.d3d11.use_instanced_draw); + if (_sg.d3d11.use_indexed_draw) { + if (use_instanced_draw) { + _sg_d3d11_DrawIndexedInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0, 0); + _sg_stats_add(d3d11.draw.num_draw_indexed_instanced, 1); + } else { + _sg_d3d11_DrawIndexed(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element, 0); + _sg_stats_add(d3d11.draw.num_draw_indexed, 1); + } + } else { + if (use_instanced_draw) { + _sg_d3d11_DrawInstanced(_sg.d3d11.ctx, (UINT)num_elements, (UINT)num_instances, (UINT)base_element, 0); + _sg_stats_add(d3d11.draw.num_draw_instanced, 1); + } else { + _sg_d3d11_Draw(_sg.d3d11.ctx, (UINT)num_elements, (UINT)base_element); + _sg_stats_add(d3d11.draw.num_draw, 1); + } + } +} + +_SOKOL_PRIVATE void _sg_d3d11_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + _sg_d3d11_Dispatch(_sg.d3d11.ctx, (UINT)num_groups_x, (UINT)num_groups_y, (UINT)num_groups_z); +} + +_SOKOL_PRIVATE void _sg_d3d11_commit(void) { + // empty +} + +_SOKOL_PRIVATE void _sg_d3d11_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(buf->d3d11.buf); + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); + _sg_stats_add(d3d11.num_map, 1); + if (SUCCEEDED(hr)) { + memcpy(d3d11_msr.pData, data->ptr, data->size); + _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + _sg_stats_add(d3d11.num_unmap, 1); + } else { + _SG_ERROR(D3D11_MAP_FOR_UPDATE_BUFFER_FAILED); + } +} + +_SOKOL_PRIVATE void _sg_d3d11_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(buf->d3d11.buf); + D3D11_MAP map_type = new_frame ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE; + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + HRESULT hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0, map_type, 0, &d3d11_msr); + _sg_stats_add(d3d11.num_map, 1); + if (SUCCEEDED(hr)) { + uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData + buf->cmn.append_pos; + memcpy(dst_ptr, data->ptr, data->size); + _sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)buf->d3d11.buf, 0); + _sg_stats_add(d3d11.num_unmap, 1); + } else { + _SG_ERROR(D3D11_MAP_FOR_APPEND_BUFFER_FAILED); + } +} + +// see: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-subresources +// also see: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11calcsubresource +_SOKOL_PRIVATE void _sg_d3d11_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(img->d3d11.res); + const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices:1; + const int num_depth_slices = (img->cmn.type == SG_IMAGETYPE_3D) ? img->cmn.num_slices:1; + UINT subres_index = 0; + HRESULT hr; + D3D11_MAPPED_SUBRESOURCE d3d11_msr; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++, subres_index++) { + SOKOL_ASSERT(subres_index < (SG_MAX_MIPMAPS * SG_MAX_TEXTUREARRAY_LAYERS)); + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + const int src_row_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + const int src_depth_pitch = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + const sg_range* subimg_data = &(data->subimage[face_index][mip_index]); + const size_t slice_size = subimg_data->size / (size_t)num_slices; + SOKOL_ASSERT(slice_size == (size_t)(src_depth_pitch * num_depth_slices)); + const size_t slice_offset = slice_size * (size_t)slice_index; + const uint8_t* slice_ptr = ((const uint8_t*)subimg_data->ptr) + slice_offset; + hr = _sg_d3d11_Map(_sg.d3d11.ctx, img->d3d11.res, subres_index, D3D11_MAP_WRITE_DISCARD, 0, &d3d11_msr); + _sg_stats_add(d3d11.num_map, 1); + if (SUCCEEDED(hr)) { + const uint8_t* src_ptr = slice_ptr; + uint8_t* dst_ptr = (uint8_t*)d3d11_msr.pData; + for (int depth_index = 0; depth_index < num_depth_slices; depth_index++) { + if (src_row_pitch == (int)d3d11_msr.RowPitch) { + const size_t copy_size = slice_size / (size_t)num_depth_slices; + SOKOL_ASSERT((copy_size * (size_t)num_depth_slices) == slice_size); + memcpy(dst_ptr, src_ptr, copy_size); + } else { + SOKOL_ASSERT(src_row_pitch < (int)d3d11_msr.RowPitch); + const uint8_t* src_row_ptr = src_ptr; + uint8_t* dst_row_ptr = dst_ptr; + for (int row_index = 0; row_index < mip_height; row_index++) { + memcpy(dst_row_ptr, src_row_ptr, (size_t)src_row_pitch); + src_row_ptr += src_row_pitch; + dst_row_ptr += d3d11_msr.RowPitch; + } + } + src_ptr += src_depth_pitch; + dst_ptr += d3d11_msr.DepthPitch; + } + _sg_d3d11_Unmap(_sg.d3d11.ctx, img->d3d11.res, subres_index); + _sg_stats_add(d3d11.num_unmap, 1); + } else { + _SG_ERROR(D3D11_MAP_FOR_UPDATE_IMAGE_FAILED); + } + } + } + } +} + +// ███ ███ ███████ ████████ █████ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ████ ██ █████ ██ ███████ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██ ███████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>metal backend +#elif defined(SOKOL_METAL) + +#if __has_feature(objc_arc) +#define _SG_OBJC_RETAIN(obj) { } +#define _SG_OBJC_RELEASE(obj) { obj = nil; } +#else +#define _SG_OBJC_RETAIN(obj) { [obj retain]; } +#define _SG_OBJC_RELEASE(obj) { [obj release]; obj = nil; } +#endif + +//-- enum translation functions ------------------------------------------------ +_SOKOL_PRIVATE MTLLoadAction _sg_mtl_load_action(sg_load_action a) { + switch (a) { + case SG_LOADACTION_CLEAR: return MTLLoadActionClear; + case SG_LOADACTION_LOAD: return MTLLoadActionLoad; + case SG_LOADACTION_DONTCARE: return MTLLoadActionDontCare; + default: SOKOL_UNREACHABLE; return (MTLLoadAction)0; + } +} + +_SOKOL_PRIVATE MTLStoreAction _sg_mtl_store_action(sg_store_action a, bool resolve) { + switch (a) { + case SG_STOREACTION_STORE: + if (resolve) { + return MTLStoreActionStoreAndMultisampleResolve; + } else { + return MTLStoreActionStore; + } + break; + case SG_STOREACTION_DONTCARE: + if (resolve) { + return MTLStoreActionMultisampleResolve; + } else { + return MTLStoreActionDontCare; + } + break; + default: SOKOL_UNREACHABLE; return (MTLStoreAction)0; + } +} + +_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_resource_options_storage_mode_managed_or_shared(void) { + #if defined(_SG_TARGET_MACOS) + if (_sg.mtl.use_shared_storage_mode) { + return MTLResourceStorageModeShared; + } else { + return MTLResourceStorageModeManaged; + } + #else + // MTLResourceStorageModeManaged is not even defined on iOS SDK + return MTLResourceStorageModeShared; + #endif +} + +_SOKOL_PRIVATE MTLResourceOptions _sg_mtl_buffer_resource_options(sg_usage usg) { + switch (usg) { + case SG_USAGE_IMMUTABLE: + return _sg_mtl_resource_options_storage_mode_managed_or_shared(); + case SG_USAGE_DYNAMIC: + case SG_USAGE_STREAM: + return MTLResourceCPUCacheModeWriteCombined | _sg_mtl_resource_options_storage_mode_managed_or_shared(); + default: + SOKOL_UNREACHABLE; + return 0; + } +} + +_SOKOL_PRIVATE MTLVertexStepFunction _sg_mtl_step_function(sg_vertex_step step) { + switch (step) { + case SG_VERTEXSTEP_PER_VERTEX: return MTLVertexStepFunctionPerVertex; + case SG_VERTEXSTEP_PER_INSTANCE: return MTLVertexStepFunctionPerInstance; + default: SOKOL_UNREACHABLE; return (MTLVertexStepFunction)0; + } +} + +_SOKOL_PRIVATE MTLVertexFormat _sg_mtl_vertex_format(sg_vertex_format fmt) { + switch (fmt) { + case SG_VERTEXFORMAT_FLOAT: return MTLVertexFormatFloat; + case SG_VERTEXFORMAT_FLOAT2: return MTLVertexFormatFloat2; + case SG_VERTEXFORMAT_FLOAT3: return MTLVertexFormatFloat3; + case SG_VERTEXFORMAT_FLOAT4: return MTLVertexFormatFloat4; + case SG_VERTEXFORMAT_INT: return MTLVertexFormatInt; + case SG_VERTEXFORMAT_INT2: return MTLVertexFormatInt2; + case SG_VERTEXFORMAT_INT3: return MTLVertexFormatInt3; + case SG_VERTEXFORMAT_INT4: return MTLVertexFormatInt4; + case SG_VERTEXFORMAT_UINT: return MTLVertexFormatUInt; + case SG_VERTEXFORMAT_UINT2: return MTLVertexFormatUInt2; + case SG_VERTEXFORMAT_UINT3: return MTLVertexFormatUInt3; + case SG_VERTEXFORMAT_UINT4: return MTLVertexFormatUInt4; + case SG_VERTEXFORMAT_BYTE4: return MTLVertexFormatChar4; + case SG_VERTEXFORMAT_BYTE4N: return MTLVertexFormatChar4Normalized; + case SG_VERTEXFORMAT_UBYTE4: return MTLVertexFormatUChar4; + case SG_VERTEXFORMAT_UBYTE4N: return MTLVertexFormatUChar4Normalized; + case SG_VERTEXFORMAT_SHORT2: return MTLVertexFormatShort2; + case SG_VERTEXFORMAT_SHORT2N: return MTLVertexFormatShort2Normalized; + case SG_VERTEXFORMAT_USHORT2: return MTLVertexFormatUShort2; + case SG_VERTEXFORMAT_USHORT2N: return MTLVertexFormatUShort2Normalized; + case SG_VERTEXFORMAT_SHORT4: return MTLVertexFormatShort4; + case SG_VERTEXFORMAT_SHORT4N: return MTLVertexFormatShort4Normalized; + case SG_VERTEXFORMAT_USHORT4: return MTLVertexFormatUShort4; + case SG_VERTEXFORMAT_USHORT4N: return MTLVertexFormatUShort4Normalized; + case SG_VERTEXFORMAT_UINT10_N2: return MTLVertexFormatUInt1010102Normalized; + case SG_VERTEXFORMAT_HALF2: return MTLVertexFormatHalf2; + case SG_VERTEXFORMAT_HALF4: return MTLVertexFormatHalf4; + default: SOKOL_UNREACHABLE; return (MTLVertexFormat)0; + } +} + +_SOKOL_PRIVATE MTLPrimitiveType _sg_mtl_primitive_type(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return MTLPrimitiveTypePoint; + case SG_PRIMITIVETYPE_LINES: return MTLPrimitiveTypeLine; + case SG_PRIMITIVETYPE_LINE_STRIP: return MTLPrimitiveTypeLineStrip; + case SG_PRIMITIVETYPE_TRIANGLES: return MTLPrimitiveTypeTriangle; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return MTLPrimitiveTypeTriangleStrip; + default: SOKOL_UNREACHABLE; return (MTLPrimitiveType)0; + } +} + +_SOKOL_PRIVATE MTLPixelFormat _sg_mtl_pixel_format(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return MTLPixelFormatR8Unorm; + case SG_PIXELFORMAT_R8SN: return MTLPixelFormatR8Snorm; + case SG_PIXELFORMAT_R8UI: return MTLPixelFormatR8Uint; + case SG_PIXELFORMAT_R8SI: return MTLPixelFormatR8Sint; + case SG_PIXELFORMAT_R16: return MTLPixelFormatR16Unorm; + case SG_PIXELFORMAT_R16SN: return MTLPixelFormatR16Snorm; + case SG_PIXELFORMAT_R16UI: return MTLPixelFormatR16Uint; + case SG_PIXELFORMAT_R16SI: return MTLPixelFormatR16Sint; + case SG_PIXELFORMAT_R16F: return MTLPixelFormatR16Float; + case SG_PIXELFORMAT_RG8: return MTLPixelFormatRG8Unorm; + case SG_PIXELFORMAT_RG8SN: return MTLPixelFormatRG8Snorm; + case SG_PIXELFORMAT_RG8UI: return MTLPixelFormatRG8Uint; + case SG_PIXELFORMAT_RG8SI: return MTLPixelFormatRG8Sint; + case SG_PIXELFORMAT_R32UI: return MTLPixelFormatR32Uint; + case SG_PIXELFORMAT_R32SI: return MTLPixelFormatR32Sint; + case SG_PIXELFORMAT_R32F: return MTLPixelFormatR32Float; + case SG_PIXELFORMAT_RG16: return MTLPixelFormatRG16Unorm; + case SG_PIXELFORMAT_RG16SN: return MTLPixelFormatRG16Snorm; + case SG_PIXELFORMAT_RG16UI: return MTLPixelFormatRG16Uint; + case SG_PIXELFORMAT_RG16SI: return MTLPixelFormatRG16Sint; + case SG_PIXELFORMAT_RG16F: return MTLPixelFormatRG16Float; + case SG_PIXELFORMAT_RGBA8: return MTLPixelFormatRGBA8Unorm; + case SG_PIXELFORMAT_SRGB8A8: return MTLPixelFormatRGBA8Unorm_sRGB; + case SG_PIXELFORMAT_RGBA8SN: return MTLPixelFormatRGBA8Snorm; + case SG_PIXELFORMAT_RGBA8UI: return MTLPixelFormatRGBA8Uint; + case SG_PIXELFORMAT_RGBA8SI: return MTLPixelFormatRGBA8Sint; + case SG_PIXELFORMAT_BGRA8: return MTLPixelFormatBGRA8Unorm; + case SG_PIXELFORMAT_RGB10A2: return MTLPixelFormatRGB10A2Unorm; + case SG_PIXELFORMAT_RG11B10F: return MTLPixelFormatRG11B10Float; + case SG_PIXELFORMAT_RGB9E5: return MTLPixelFormatRGB9E5Float; + case SG_PIXELFORMAT_RG32UI: return MTLPixelFormatRG32Uint; + case SG_PIXELFORMAT_RG32SI: return MTLPixelFormatRG32Sint; + case SG_PIXELFORMAT_RG32F: return MTLPixelFormatRG32Float; + case SG_PIXELFORMAT_RGBA16: return MTLPixelFormatRGBA16Unorm; + case SG_PIXELFORMAT_RGBA16SN: return MTLPixelFormatRGBA16Snorm; + case SG_PIXELFORMAT_RGBA16UI: return MTLPixelFormatRGBA16Uint; + case SG_PIXELFORMAT_RGBA16SI: return MTLPixelFormatRGBA16Sint; + case SG_PIXELFORMAT_RGBA16F: return MTLPixelFormatRGBA16Float; + case SG_PIXELFORMAT_RGBA32UI: return MTLPixelFormatRGBA32Uint; + case SG_PIXELFORMAT_RGBA32SI: return MTLPixelFormatRGBA32Sint; + case SG_PIXELFORMAT_RGBA32F: return MTLPixelFormatRGBA32Float; + case SG_PIXELFORMAT_DEPTH: return MTLPixelFormatDepth32Float; + case SG_PIXELFORMAT_DEPTH_STENCIL: return MTLPixelFormatDepth32Float_Stencil8; + #if defined(_SG_TARGET_MACOS) + case SG_PIXELFORMAT_BC1_RGBA: return MTLPixelFormatBC1_RGBA; + case SG_PIXELFORMAT_BC2_RGBA: return MTLPixelFormatBC2_RGBA; + case SG_PIXELFORMAT_BC3_RGBA: return MTLPixelFormatBC3_RGBA; + case SG_PIXELFORMAT_BC3_SRGBA: return MTLPixelFormatBC3_RGBA_sRGB; + case SG_PIXELFORMAT_BC4_R: return MTLPixelFormatBC4_RUnorm; + case SG_PIXELFORMAT_BC4_RSN: return MTLPixelFormatBC4_RSnorm; + case SG_PIXELFORMAT_BC5_RG: return MTLPixelFormatBC5_RGUnorm; + case SG_PIXELFORMAT_BC5_RGSN: return MTLPixelFormatBC5_RGSnorm; + case SG_PIXELFORMAT_BC6H_RGBF: return MTLPixelFormatBC6H_RGBFloat; + case SG_PIXELFORMAT_BC6H_RGBUF: return MTLPixelFormatBC6H_RGBUfloat; + case SG_PIXELFORMAT_BC7_RGBA: return MTLPixelFormatBC7_RGBAUnorm; + case SG_PIXELFORMAT_BC7_SRGBA: return MTLPixelFormatBC7_RGBAUnorm_sRGB; + #else + case SG_PIXELFORMAT_ETC2_RGB8: return MTLPixelFormatETC2_RGB8; + case SG_PIXELFORMAT_ETC2_SRGB8: return MTLPixelFormatETC2_RGB8_sRGB; + case SG_PIXELFORMAT_ETC2_RGB8A1: return MTLPixelFormatETC2_RGB8A1; + case SG_PIXELFORMAT_ETC2_RGBA8: return MTLPixelFormatEAC_RGBA8; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return MTLPixelFormatEAC_RGBA8_sRGB; + case SG_PIXELFORMAT_EAC_R11: return MTLPixelFormatEAC_R11Unorm; + case SG_PIXELFORMAT_EAC_R11SN: return MTLPixelFormatEAC_R11Snorm; + case SG_PIXELFORMAT_EAC_RG11: return MTLPixelFormatEAC_RG11Unorm; + case SG_PIXELFORMAT_EAC_RG11SN: return MTLPixelFormatEAC_RG11Snorm; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return MTLPixelFormatASTC_4x4_LDR; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return MTLPixelFormatASTC_4x4_sRGB; + #endif + default: return MTLPixelFormatInvalid; + } +} + +_SOKOL_PRIVATE MTLColorWriteMask _sg_mtl_color_write_mask(sg_color_mask m) { + MTLColorWriteMask mtl_mask = MTLColorWriteMaskNone; + if (m & SG_COLORMASK_R) { + mtl_mask |= MTLColorWriteMaskRed; + } + if (m & SG_COLORMASK_G) { + mtl_mask |= MTLColorWriteMaskGreen; + } + if (m & SG_COLORMASK_B) { + mtl_mask |= MTLColorWriteMaskBlue; + } + if (m & SG_COLORMASK_A) { + mtl_mask |= MTLColorWriteMaskAlpha; + } + return mtl_mask; +} + +_SOKOL_PRIVATE MTLBlendOperation _sg_mtl_blend_op(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return MTLBlendOperationAdd; + case SG_BLENDOP_SUBTRACT: return MTLBlendOperationSubtract; + case SG_BLENDOP_REVERSE_SUBTRACT: return MTLBlendOperationReverseSubtract; + case SG_BLENDOP_MIN: return MTLBlendOperationMin; + case SG_BLENDOP_MAX: return MTLBlendOperationMax; + default: SOKOL_UNREACHABLE; return (MTLBlendOperation)0; + } +} + +_SOKOL_PRIVATE MTLBlendFactor _sg_mtl_blend_factor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return MTLBlendFactorZero; + case SG_BLENDFACTOR_ONE: return MTLBlendFactorOne; + case SG_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor; + case SG_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha; + case SG_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor; + case SG_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return MTLBlendFactorSourceAlphaSaturated; + case SG_BLENDFACTOR_BLEND_COLOR: return MTLBlendFactorBlendColor; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return MTLBlendFactorOneMinusBlendColor; + case SG_BLENDFACTOR_BLEND_ALPHA: return MTLBlendFactorBlendAlpha; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return MTLBlendFactorOneMinusBlendAlpha; + default: SOKOL_UNREACHABLE; return (MTLBlendFactor)0; + } +} + +_SOKOL_PRIVATE MTLCompareFunction _sg_mtl_compare_func(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return MTLCompareFunctionNever; + case SG_COMPAREFUNC_LESS: return MTLCompareFunctionLess; + case SG_COMPAREFUNC_EQUAL: return MTLCompareFunctionEqual; + case SG_COMPAREFUNC_LESS_EQUAL: return MTLCompareFunctionLessEqual; + case SG_COMPAREFUNC_GREATER: return MTLCompareFunctionGreater; + case SG_COMPAREFUNC_NOT_EQUAL: return MTLCompareFunctionNotEqual; + case SG_COMPAREFUNC_GREATER_EQUAL: return MTLCompareFunctionGreaterEqual; + case SG_COMPAREFUNC_ALWAYS: return MTLCompareFunctionAlways; + default: SOKOL_UNREACHABLE; return (MTLCompareFunction)0; + } +} + +_SOKOL_PRIVATE MTLStencilOperation _sg_mtl_stencil_op(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return MTLStencilOperationKeep; + case SG_STENCILOP_ZERO: return MTLStencilOperationZero; + case SG_STENCILOP_REPLACE: return MTLStencilOperationReplace; + case SG_STENCILOP_INCR_CLAMP: return MTLStencilOperationIncrementClamp; + case SG_STENCILOP_DECR_CLAMP: return MTLStencilOperationDecrementClamp; + case SG_STENCILOP_INVERT: return MTLStencilOperationInvert; + case SG_STENCILOP_INCR_WRAP: return MTLStencilOperationIncrementWrap; + case SG_STENCILOP_DECR_WRAP: return MTLStencilOperationDecrementWrap; + default: SOKOL_UNREACHABLE; return (MTLStencilOperation)0; + } +} + +_SOKOL_PRIVATE MTLCullMode _sg_mtl_cull_mode(sg_cull_mode m) { + switch (m) { + case SG_CULLMODE_NONE: return MTLCullModeNone; + case SG_CULLMODE_FRONT: return MTLCullModeFront; + case SG_CULLMODE_BACK: return MTLCullModeBack; + default: SOKOL_UNREACHABLE; return (MTLCullMode)0; + } +} + +_SOKOL_PRIVATE MTLWinding _sg_mtl_winding(sg_face_winding w) { + switch (w) { + case SG_FACEWINDING_CW: return MTLWindingClockwise; + case SG_FACEWINDING_CCW: return MTLWindingCounterClockwise; + default: SOKOL_UNREACHABLE; return (MTLWinding)0; + } +} + +_SOKOL_PRIVATE MTLIndexType _sg_mtl_index_type(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_UINT16: return MTLIndexTypeUInt16; + case SG_INDEXTYPE_UINT32: return MTLIndexTypeUInt32; + default: SOKOL_UNREACHABLE; return (MTLIndexType)0; + } +} + +_SOKOL_PRIVATE int _sg_mtl_index_size(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_NONE: return 0; + case SG_INDEXTYPE_UINT16: return 2; + case SG_INDEXTYPE_UINT32: return 4; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE MTLTextureType _sg_mtl_texture_type(sg_image_type t) { + switch (t) { + case SG_IMAGETYPE_2D: return MTLTextureType2D; + case SG_IMAGETYPE_CUBE: return MTLTextureTypeCube; + case SG_IMAGETYPE_3D: return MTLTextureType3D; + case SG_IMAGETYPE_ARRAY: return MTLTextureType2DArray; + default: SOKOL_UNREACHABLE; return (MTLTextureType)0; + } +} + +_SOKOL_PRIVATE MTLSamplerAddressMode _sg_mtl_address_mode(sg_wrap w) { + if (_sg.features.image_clamp_to_border) { + if (@available(macOS 12.0, iOS 14.0, *)) { + // border color feature available + switch (w) { + case SG_WRAP_REPEAT: return MTLSamplerAddressModeRepeat; + case SG_WRAP_CLAMP_TO_EDGE: return MTLSamplerAddressModeClampToEdge; + case SG_WRAP_CLAMP_TO_BORDER: return MTLSamplerAddressModeClampToBorderColor; + case SG_WRAP_MIRRORED_REPEAT: return MTLSamplerAddressModeMirrorRepeat; + default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0; + } + } + } + // fallthrough: clamp to border no supported + switch (w) { + case SG_WRAP_REPEAT: return MTLSamplerAddressModeRepeat; + case SG_WRAP_CLAMP_TO_EDGE: return MTLSamplerAddressModeClampToEdge; + case SG_WRAP_CLAMP_TO_BORDER: return MTLSamplerAddressModeClampToEdge; + case SG_WRAP_MIRRORED_REPEAT: return MTLSamplerAddressModeMirrorRepeat; + default: SOKOL_UNREACHABLE; return (MTLSamplerAddressMode)0; + } +} + +_SOKOL_PRIVATE API_AVAILABLE(ios(14.0), macos(12.0)) MTLSamplerBorderColor _sg_mtl_border_color(sg_border_color c) { + switch (c) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: return MTLSamplerBorderColorTransparentBlack; + case SG_BORDERCOLOR_OPAQUE_BLACK: return MTLSamplerBorderColorOpaqueBlack; + case SG_BORDERCOLOR_OPAQUE_WHITE: return MTLSamplerBorderColorOpaqueWhite; + default: SOKOL_UNREACHABLE; return (MTLSamplerBorderColor)0; + } +} + +_SOKOL_PRIVATE MTLSamplerMinMagFilter _sg_mtl_minmag_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return MTLSamplerMinMagFilterNearest; + case SG_FILTER_LINEAR: + return MTLSamplerMinMagFilterLinear; + default: + SOKOL_UNREACHABLE; return (MTLSamplerMinMagFilter)0; + } +} + +_SOKOL_PRIVATE MTLSamplerMipFilter _sg_mtl_mipmap_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return MTLSamplerMipFilterNearest; + case SG_FILTER_LINEAR: + return MTLSamplerMipFilterLinear; + default: + SOKOL_UNREACHABLE; return (MTLSamplerMipFilter)0; + } +} + +_SOKOL_PRIVATE size_t _sg_mtl_vertexbuffer_bindslot(size_t sokol_bindslot) { + return sokol_bindslot + _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS; +} + +//-- a pool for all Metal resource objects, with deferred release queue --------- +_SOKOL_PRIVATE void _sg_mtl_init_pool(const sg_desc* desc) { + _sg.mtl.idpool.num_slots = 2 * + ( + 2 * desc->buffer_pool_size + + 4 * desc->image_pool_size + + 1 * desc->sampler_pool_size + + 4 * desc->shader_pool_size + + 2 * desc->pipeline_pool_size + + desc->attachments_pool_size + + 128 + ); + _sg.mtl.idpool.pool = [NSMutableArray arrayWithCapacity:(NSUInteger)_sg.mtl.idpool.num_slots]; + _SG_OBJC_RETAIN(_sg.mtl.idpool.pool); + NSNull* null = [NSNull null]; + for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { + [_sg.mtl.idpool.pool addObject:null]; + } + SOKOL_ASSERT([_sg.mtl.idpool.pool count] == (NSUInteger)_sg.mtl.idpool.num_slots); + // a queue of currently free slot indices + _sg.mtl.idpool.free_queue_top = 0; + _sg.mtl.idpool.free_queue = (int*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(int)); + // pool slot 0 is reserved! + for (int i = _sg.mtl.idpool.num_slots-1; i >= 1; i--) { + _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = i; + } + // a circular queue which holds release items (frame index when a resource is to be released, and the resource's pool index + _sg.mtl.idpool.release_queue_front = 0; + _sg.mtl.idpool.release_queue_back = 0; + _sg.mtl.idpool.release_queue = (_sg_mtl_release_item_t*)_sg_malloc_clear((size_t)_sg.mtl.idpool.num_slots * sizeof(_sg_mtl_release_item_t)); + for (int i = 0; i < _sg.mtl.idpool.num_slots; i++) { + _sg.mtl.idpool.release_queue[i].frame_index = 0; + _sg.mtl.idpool.release_queue[i].slot_index = _SG_MTL_INVALID_SLOT_INDEX; + } +} + +_SOKOL_PRIVATE void _sg_mtl_destroy_pool(void) { + _sg_free(_sg.mtl.idpool.release_queue); _sg.mtl.idpool.release_queue = 0; + _sg_free(_sg.mtl.idpool.free_queue); _sg.mtl.idpool.free_queue = 0; + _SG_OBJC_RELEASE(_sg.mtl.idpool.pool); +} + +// get a new free resource pool slot +_SOKOL_PRIVATE int _sg_mtl_alloc_pool_slot(void) { + SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top > 0); + const int slot_index = _sg.mtl.idpool.free_queue[--_sg.mtl.idpool.free_queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + return slot_index; +} + +// put a free resource pool slot back into the free-queue +_SOKOL_PRIVATE void _sg_mtl_free_pool_slot(int slot_index) { + SOKOL_ASSERT(_sg.mtl.idpool.free_queue_top < _sg.mtl.idpool.num_slots); + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + _sg.mtl.idpool.free_queue[_sg.mtl.idpool.free_queue_top++] = slot_index; +} + +// add an MTLResource to the pool, return pool index or 0 if input was 'nil' +_SOKOL_PRIVATE int _sg_mtl_add_resource(id res) { + if (nil == res) { + return _SG_MTL_INVALID_SLOT_INDEX; + } + _sg_stats_add(metal.idpool.num_added, 1); + const int slot_index = _sg_mtl_alloc_pool_slot(); + // NOTE: the NSMutableArray will take ownership of its items + SOKOL_ASSERT([NSNull null] == _sg.mtl.idpool.pool[(NSUInteger)slot_index]); + _sg.mtl.idpool.pool[(NSUInteger)slot_index] = res; + return slot_index; +} + +/* mark an MTLResource for release, this will put the resource into the + deferred-release queue, and the resource will then be released N frames later, + the special pool index 0 will be ignored (this means that a nil + value was provided to _sg_mtl_add_resource() +*/ +_SOKOL_PRIVATE void _sg_mtl_release_resource(uint32_t frame_index, int slot_index) { + if (slot_index == _SG_MTL_INVALID_SLOT_INDEX) { + return; + } + _sg_stats_add(metal.idpool.num_released, 1); + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + SOKOL_ASSERT([NSNull null] != _sg.mtl.idpool.pool[(NSUInteger)slot_index]); + int release_index = _sg.mtl.idpool.release_queue_front++; + if (_sg.mtl.idpool.release_queue_front >= _sg.mtl.idpool.num_slots) { + // wrap-around + _sg.mtl.idpool.release_queue_front = 0; + } + // release queue full? + SOKOL_ASSERT(_sg.mtl.idpool.release_queue_front != _sg.mtl.idpool.release_queue_back); + SOKOL_ASSERT(0 == _sg.mtl.idpool.release_queue[release_index].frame_index); + const uint32_t safe_to_release_frame_index = frame_index + SG_NUM_INFLIGHT_FRAMES + 1; + _sg.mtl.idpool.release_queue[release_index].frame_index = safe_to_release_frame_index; + _sg.mtl.idpool.release_queue[release_index].slot_index = slot_index; +} + +// run garbage-collection pass on all resources in the release-queue +_SOKOL_PRIVATE void _sg_mtl_garbage_collect(uint32_t frame_index) { + while (_sg.mtl.idpool.release_queue_back != _sg.mtl.idpool.release_queue_front) { + if (frame_index < _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index) { + // don't need to check further, release-items past this are too young + break; + } + _sg_stats_add(metal.idpool.num_garbage_collected, 1); + // safe to release this resource + const int slot_index = _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index; + SOKOL_ASSERT((slot_index > 0) && (slot_index < _sg.mtl.idpool.num_slots)); + // note: the NSMutableArray takes ownership of its items, assigning an NSNull object will + // release the object, no matter if using ARC or not + SOKOL_ASSERT(_sg.mtl.idpool.pool[(NSUInteger)slot_index] != [NSNull null]); + _sg.mtl.idpool.pool[(NSUInteger)slot_index] = [NSNull null]; + // put the now free pool index back on the free queue + _sg_mtl_free_pool_slot(slot_index); + // reset the release queue slot and advance the back index + _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].frame_index = 0; + _sg.mtl.idpool.release_queue[_sg.mtl.idpool.release_queue_back].slot_index = _SG_MTL_INVALID_SLOT_INDEX; + _sg.mtl.idpool.release_queue_back++; + if (_sg.mtl.idpool.release_queue_back >= _sg.mtl.idpool.num_slots) { + // wrap-around + _sg.mtl.idpool.release_queue_back = 0; + } + } +} + +_SOKOL_PRIVATE id _sg_mtl_id(int slot_index) { + return _sg.mtl.idpool.pool[(NSUInteger)slot_index]; +} + +_SOKOL_PRIVATE void _sg_mtl_clear_state_cache(void) { + _sg_clear(&_sg.mtl.state_cache, sizeof(_sg.mtl.state_cache)); +} + +// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf +_SOKOL_PRIVATE void _sg_mtl_init_caps(void) { + #if defined(_SG_TARGET_MACOS) + _sg.backend = SG_BACKEND_METAL_MACOS; + #elif defined(_SG_TARGET_IOS) + #if defined(_SG_TARGET_IOS_SIMULATOR) + _sg.backend = SG_BACKEND_METAL_SIMULATOR; + #else + _sg.backend = SG_BACKEND_METAL_IOS; + #endif + #endif + _sg.features.origin_top_left = true; + _sg.features.mrt_independent_blend_state = true; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = true; + _sg.features.msaa_image_bindings = true; + + _sg.features.image_clamp_to_border = false; + #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 120000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) + if (@available(macOS 12.0, iOS 14.0, *)) { + _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple7] + || [_sg.mtl.device supportsFamily:MTLGPUFamilyMac2]; + #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 130000) || (__IPHONE_OS_VERSION_MAX_ALLOWED >= 160000) + if (!_sg.features.image_clamp_to_border) { + if (@available(macOS 13.0, iOS 16.0, *)) { + _sg.features.image_clamp_to_border = [_sg.mtl.device supportsFamily:MTLGPUFamilyMetal3]; + } + } + #endif + } + #endif + + #if defined(_SG_TARGET_MACOS) + _sg.limits.max_image_size_2d = 16 * 1024; + _sg.limits.max_image_size_cube = 16 * 1024; + _sg.limits.max_image_size_3d = 2 * 1024; + _sg.limits.max_image_size_array = 16 * 1024; + _sg.limits.max_image_array_layers = 2 * 1024; + #else + // FIXME: newer iOS devices support 16k textures + _sg.limits.max_image_size_2d = 8 * 1024; + _sg.limits.max_image_size_cube = 8 * 1024; + _sg.limits.max_image_size_3d = 2 * 1024; + _sg.limits.max_image_size_array = 8 * 1024; + _sg.limits.max_image_array_layers = 2 * 1024; + #endif + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R8SI]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16SN]); + #else + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16]); + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_R16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_R16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R32F]); + #else + _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_R32F]); + #endif + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16SN]); + #else + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16]); + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RG16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #else + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + #endif + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG32F]); + #else + _sg_pixelformat_sbr(&_sg.formats[SG_PIXELFORMAT_RG32F]); + #endif + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); + #else + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16]); + _sg_pixelformat_sfbr(&_sg.formats[SG_PIXELFORMAT_RGBA16SN]); + #endif + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_srm(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + #else + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + #endif + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); + #if defined(_SG_TARGET_MACOS) + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]); + #else + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]); + + #endif +} + +//-- main Metal backend state and functions ------------------------------------ +_SOKOL_PRIVATE void _sg_mtl_setup_backend(const sg_desc* desc) { + // assume already zero-initialized + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->environment.metal.device); + SOKOL_ASSERT(desc->uniform_buffer_size > 0); + _sg_mtl_init_pool(desc); + _sg_mtl_clear_state_cache(); + _sg.mtl.valid = true; + _sg.mtl.ub_size = desc->uniform_buffer_size; + _sg.mtl.sem = dispatch_semaphore_create(SG_NUM_INFLIGHT_FRAMES); + _sg.mtl.device = (__bridge id) desc->environment.metal.device; + _sg.mtl.cmd_queue = [_sg.mtl.device newCommandQueue]; + + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + _sg.mtl.uniform_buffers[i] = [_sg.mtl.device + newBufferWithLength:(NSUInteger)_sg.mtl.ub_size + options:MTLResourceCPUCacheModeWriteCombined|MTLResourceStorageModeShared + ]; + #if defined(SOKOL_DEBUG) + _sg.mtl.uniform_buffers[i].label = [NSString stringWithFormat:@"sg-uniform-buffer.%d", i]; + #endif + } + + if (desc->mtl_force_managed_storage_mode) { + _sg.mtl.use_shared_storage_mode = false; + } else if (@available(macOS 10.15, iOS 13.0, *)) { + // on Intel Macs, always use managed resources even though the + // device says it supports unified memory (because of texture restrictions) + const bool is_apple_gpu = [_sg.mtl.device supportsFamily:MTLGPUFamilyApple1]; + if (!is_apple_gpu) { + _sg.mtl.use_shared_storage_mode = false; + } else { + _sg.mtl.use_shared_storage_mode = true; + } + } else { + #if defined(_SG_TARGET_MACOS) + _sg.mtl.use_shared_storage_mode = false; + #else + _sg.mtl.use_shared_storage_mode = true; + #endif + } + _sg_mtl_init_caps(); +} + +_SOKOL_PRIVATE void _sg_mtl_discard_backend(void) { + SOKOL_ASSERT(_sg.mtl.valid); + // wait for the last frame to finish + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); + } + // semaphore must be "relinquished" before destruction + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + dispatch_semaphore_signal(_sg.mtl.sem); + } + _sg_mtl_garbage_collect(_sg.frame_index + SG_NUM_INFLIGHT_FRAMES + 2); + _sg_mtl_destroy_pool(); + _sg.mtl.valid = false; + + _SG_OBJC_RELEASE(_sg.mtl.sem); + _SG_OBJC_RELEASE(_sg.mtl.device); + _SG_OBJC_RELEASE(_sg.mtl.cmd_queue); + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + _SG_OBJC_RELEASE(_sg.mtl.uniform_buffers[i]); + } + // NOTE: MTLCommandBuffer, MTLRenderCommandEncoder and MTLComputeCommandEncoder are auto-released + _sg.mtl.cmd_buffer = nil; + _sg.mtl.render_cmd_encoder = nil; + _sg.mtl.compute_cmd_encoder = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_reset_state_cache(void) { + _sg_mtl_clear_state_cache(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + SOKOL_ASSERT(buf->cmn.size > 0); + const bool injected = (0 != desc->mtl_buffers[0]); + MTLResourceOptions mtl_options = _sg_mtl_buffer_resource_options(buf->cmn.usage); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + id mtl_buf; + if (injected) { + SOKOL_ASSERT(desc->mtl_buffers[slot]); + mtl_buf = (__bridge id) desc->mtl_buffers[slot]; + } else { + if (desc->data.ptr) { + SOKOL_ASSERT(desc->data.size > 0); + mtl_buf = [_sg.mtl.device newBufferWithBytes:desc->data.ptr length:(NSUInteger)buf->cmn.size options:mtl_options]; + } else { + // this is guaranteed to zero-initialize the buffer + mtl_buf = [_sg.mtl.device newBufferWithLength:(NSUInteger)buf->cmn.size options:mtl_options]; + } + if (nil == mtl_buf) { + _SG_ERROR(METAL_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_buf.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot]; + } + #endif + buf->mtl.buf[slot] = _sg_mtl_add_resource(mtl_buf); + _SG_OBJC_RELEASE(mtl_buf); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + for (int slot = 0; slot < buf->cmn.num_slots; slot++) { + // it's valid to call release resource with '0' + _sg_mtl_release_resource(_sg.frame_index, buf->mtl.buf[slot]); + } +} + +_SOKOL_PRIVATE void _sg_mtl_copy_image_data(const _sg_image_t* img, __unsafe_unretained id mtl_tex, const sg_image_data* data) { + const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6:1; + const int num_slices = (img->cmn.type == SG_IMAGETYPE_ARRAY) ? img->cmn.num_slices : 1; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + SOKOL_ASSERT(data->subimage[face_index][mip_index].ptr); + SOKOL_ASSERT(data->subimage[face_index][mip_index].size > 0); + const uint8_t* data_ptr = (const uint8_t*)data->subimage[face_index][mip_index].ptr; + const int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + const int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + int bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + int bytes_per_slice = _sg_surface_pitch(img->cmn.pixel_format, mip_width, mip_height, 1); + /* bytesPerImage special case: https://developer.apple.com/documentation/metal/mtltexture/1515679-replaceregion + + "Supply a nonzero value only when you copy data to a MTLTextureType3D type texture" + */ + MTLRegion region; + int bytes_per_image; + if (img->cmn.type == SG_IMAGETYPE_3D) { + const int mip_depth = _sg_miplevel_dim(img->cmn.num_slices, mip_index); + region = MTLRegionMake3D(0, 0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height, (NSUInteger)mip_depth); + bytes_per_image = bytes_per_slice; + // FIXME: apparently the minimal bytes_per_image size for 3D texture is 4 KByte... somehow need to handle this + } else { + region = MTLRegionMake2D(0, 0, (NSUInteger)mip_width, (NSUInteger)mip_height); + bytes_per_image = 0; + } + + for (int slice_index = 0; slice_index < num_slices; slice_index++) { + const int mtl_slice_index = (img->cmn.type == SG_IMAGETYPE_CUBE) ? face_index : slice_index; + const int slice_offset = slice_index * bytes_per_slice; + SOKOL_ASSERT((slice_offset + bytes_per_slice) <= (int)data->subimage[face_index][mip_index].size); + [mtl_tex replaceRegion:region + mipmapLevel:(NSUInteger)mip_index + slice:(NSUInteger)mtl_slice_index + withBytes:data_ptr + slice_offset + bytesPerRow:(NSUInteger)bytes_per_row + bytesPerImage:(NSUInteger)bytes_per_image]; + } + } + } +} + +// initialize MTLTextureDescriptor with common attributes +_SOKOL_PRIVATE bool _sg_mtl_init_texdesc_common(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { + mtl_desc.textureType = _sg_mtl_texture_type(img->cmn.type); + mtl_desc.pixelFormat = _sg_mtl_pixel_format(img->cmn.pixel_format); + if (MTLPixelFormatInvalid == mtl_desc.pixelFormat) { + _SG_ERROR(METAL_TEXTURE_FORMAT_NOT_SUPPORTED); + return false; + } + mtl_desc.width = (NSUInteger)img->cmn.width; + mtl_desc.height = (NSUInteger)img->cmn.height; + if (SG_IMAGETYPE_3D == img->cmn.type) { + mtl_desc.depth = (NSUInteger)img->cmn.num_slices; + } else { + mtl_desc.depth = 1; + } + mtl_desc.mipmapLevelCount = (NSUInteger)img->cmn.num_mipmaps; + if (SG_IMAGETYPE_ARRAY == img->cmn.type) { + mtl_desc.arrayLength = (NSUInteger)img->cmn.num_slices; + } else { + mtl_desc.arrayLength = 1; + } + mtl_desc.usage = MTLTextureUsageShaderRead; + MTLResourceOptions res_options = 0; + if (img->cmn.usage != SG_USAGE_IMMUTABLE) { + res_options |= MTLResourceCPUCacheModeWriteCombined; + } + res_options |= _sg_mtl_resource_options_storage_mode_managed_or_shared(); + mtl_desc.resourceOptions = res_options; + return true; +} + +// initialize MTLTextureDescriptor with rendertarget attributes +_SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { + SOKOL_ASSERT(img->cmn.render_target); + _SOKOL_UNUSED(img); + mtl_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; + mtl_desc.resourceOptions = MTLResourceStorageModePrivate; +} + +// initialize MTLTextureDescriptor with MSAA attributes +_SOKOL_PRIVATE void _sg_mtl_init_texdesc_rt_msaa(MTLTextureDescriptor* mtl_desc, _sg_image_t* img) { + SOKOL_ASSERT(img->cmn.sample_count > 1); + mtl_desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; + mtl_desc.resourceOptions = MTLResourceStorageModePrivate; + mtl_desc.textureType = MTLTextureType2DMultisample; + mtl_desc.sampleCount = (NSUInteger)img->cmn.sample_count; +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + const bool injected = (0 != desc->mtl_textures[0]); + + // first initialize all Metal resource pool slots to 'empty' + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + img->mtl.tex[i] = _sg_mtl_add_resource(nil); + } + + // initialize a Metal texture descriptor + MTLTextureDescriptor* mtl_desc = [[MTLTextureDescriptor alloc] init]; + if (!_sg_mtl_init_texdesc_common(mtl_desc, img)) { + _SG_OBJC_RELEASE(mtl_desc); + return SG_RESOURCESTATE_FAILED; + } + if (img->cmn.render_target) { + if (img->cmn.sample_count > 1) { + _sg_mtl_init_texdesc_rt_msaa(mtl_desc, img); + } else { + _sg_mtl_init_texdesc_rt(mtl_desc, img); + } + } + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + id mtl_tex; + if (injected) { + SOKOL_ASSERT(desc->mtl_textures[slot]); + mtl_tex = (__bridge id) desc->mtl_textures[slot]; + } else { + mtl_tex = [_sg.mtl.device newTextureWithDescriptor:mtl_desc]; + if (nil == mtl_tex) { + _SG_OBJC_RELEASE(mtl_desc); + _SG_ERROR(METAL_CREATE_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + if ((img->cmn.usage == SG_USAGE_IMMUTABLE) && !img->cmn.render_target) { + _sg_mtl_copy_image_data(img, mtl_tex, &desc->data); + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_tex.label = [NSString stringWithFormat:@"%s.%d", desc->label, slot]; + } + #endif + img->mtl.tex[slot] = _sg_mtl_add_resource(mtl_tex); + _SG_OBJC_RELEASE(mtl_tex); + } + _SG_OBJC_RELEASE(mtl_desc); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + // it's valid to call release resource with a 'null resource' + for (int slot = 0; slot < img->cmn.num_slots; slot++) { + _sg_mtl_release_resource(_sg.frame_index, img->mtl.tex[slot]); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + id mtl_smp; + const bool injected = (0 != desc->mtl_sampler); + if (injected) { + SOKOL_ASSERT(desc->mtl_sampler); + mtl_smp = (__bridge id) desc->mtl_sampler; + } else { + MTLSamplerDescriptor* mtl_desc = [[MTLSamplerDescriptor alloc] init]; + mtl_desc.sAddressMode = _sg_mtl_address_mode(desc->wrap_u); + mtl_desc.tAddressMode = _sg_mtl_address_mode(desc->wrap_v); + mtl_desc.rAddressMode = _sg_mtl_address_mode(desc->wrap_w); + if (_sg.features.image_clamp_to_border) { + if (@available(macOS 12.0, iOS 14.0, *)) { + mtl_desc.borderColor = _sg_mtl_border_color(desc->border_color); + } + } + mtl_desc.minFilter = _sg_mtl_minmag_filter(desc->min_filter); + mtl_desc.magFilter = _sg_mtl_minmag_filter(desc->mag_filter); + mtl_desc.mipFilter = _sg_mtl_mipmap_filter(desc->mipmap_filter); + mtl_desc.lodMinClamp = desc->min_lod; + mtl_desc.lodMaxClamp = desc->max_lod; + // FIXME: lodAverage? + mtl_desc.maxAnisotropy = desc->max_anisotropy; + mtl_desc.normalizedCoordinates = YES; + mtl_desc.compareFunction = _sg_mtl_compare_func(desc->compare); + #if defined(SOKOL_DEBUG) + if (desc->label) { + mtl_desc.label = [NSString stringWithUTF8String:desc->label]; + } + #endif + mtl_smp = [_sg.mtl.device newSamplerStateWithDescriptor:mtl_desc]; + _SG_OBJC_RELEASE(mtl_desc); + if (nil == mtl_smp) { + _SG_ERROR(METAL_CREATE_SAMPLER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + smp->mtl.sampler_state = _sg_mtl_add_resource(mtl_smp); + _SG_OBJC_RELEASE(mtl_smp); + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + // it's valid to call release resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, smp->mtl.sampler_state); +} + +_SOKOL_PRIVATE id _sg_mtl_compile_library(const char* src) { + NSError* err = NULL; + id lib = [_sg.mtl.device + newLibraryWithSource:[NSString stringWithUTF8String:src] + options:nil + error:&err + ]; + if (err) { + _SG_ERROR(METAL_SHADER_COMPILATION_FAILED); + _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); + } + return lib; +} + +_SOKOL_PRIVATE id _sg_mtl_library_from_bytecode(const void* ptr, size_t num_bytes) { + NSError* err = NULL; + dispatch_data_t lib_data = dispatch_data_create(ptr, num_bytes, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + id lib = [_sg.mtl.device newLibraryWithData:lib_data error:&err]; + if (err) { + _SG_ERROR(METAL_SHADER_CREATION_FAILED); + _SG_LOGMSG(METAL_SHADER_COMPILATION_OUTPUT, [err.localizedDescription UTF8String]); + } + _SG_OBJC_RELEASE(lib_data); + return lib; +} + +_SOKOL_PRIVATE bool _sg_mtl_create_shader_func(const sg_shader_function* func, const char* label, const char* label_ext, _sg_mtl_shader_func_t* res) { + SOKOL_ASSERT(res->mtl_lib == _SG_MTL_INVALID_SLOT_INDEX); + SOKOL_ASSERT(res->mtl_func == _SG_MTL_INVALID_SLOT_INDEX); + id mtl_lib = nil; + if (func->bytecode.ptr) { + SOKOL_ASSERT(func->bytecode.size > 0); + mtl_lib = _sg_mtl_library_from_bytecode(func->bytecode.ptr, func->bytecode.size); + } else if (func->source) { + mtl_lib = _sg_mtl_compile_library(func->source); + } + if (mtl_lib == nil) { + return false; + } + #if defined(SOKOL_DEBUG) + if (label) { + SOKOL_ASSERT(label_ext); + mtl_lib.label = [NSString stringWithFormat:@"%s.%s", label, label_ext]; + } + #else + _SOKOL_UNUSED(label); + _SOKOL_UNUSED(label_ext); + #endif + SOKOL_ASSERT(func->entry); + id mtl_func = [mtl_lib newFunctionWithName:[NSString stringWithUTF8String:func->entry]]; + if (mtl_func == nil) { + _SG_ERROR(METAL_SHADER_ENTRY_NOT_FOUND); + _SG_OBJC_RELEASE(mtl_lib); + return false; + } + res->mtl_lib = _sg_mtl_add_resource(mtl_lib); + res->mtl_func = _sg_mtl_add_resource(mtl_func); + _SG_OBJC_RELEASE(mtl_lib); + _SG_OBJC_RELEASE(mtl_func); + return true; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_shader_func(const _sg_mtl_shader_func_t* func) { + // it is valid to call _sg_mtl_release_resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, func->mtl_func); + _sg_mtl_release_resource(_sg.frame_index, func->mtl_lib); +} + +// NOTE: this is an out-of-range check for MSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_mtl_ensure_msl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (desc->uniform_blocks[i].msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_BINDINGS) { + _SG_ERROR(METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS) { + _SG_ERROR(METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (desc->images[i].msl_texture_n >= _SG_MTL_MAX_STAGE_IMAGE_BINDINGS) { + _SG_ERROR(METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (desc->samplers[i].msl_sampler_n >= _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS) { + _SG_ERROR(METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE); + return false; + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + + // do a MSL bindslot range check also in release mode, and if that fails, + // also fail shader creation + if (!_sg_mtl_ensure_msl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + shd->mtl.threads_per_threadgroup = MTLSizeMake( + (NSUInteger)desc->mtl_threads_per_threadgroup.x, + (NSUInteger)desc->mtl_threads_per_threadgroup.y, + (NSUInteger)desc->mtl_threads_per_threadgroup.z); + + // copy resource bindslot mappings + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + shd->mtl.ub_buffer_n[i] = desc->uniform_blocks[i].msl_buffer_n; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + shd->mtl.sbuf_buffer_n[i] = desc->storage_buffers[i].msl_buffer_n; + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + shd->mtl.img_texture_n[i] = desc->images[i].msl_texture_n; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + shd->mtl.smp_sampler_n[i] = desc->samplers[i].msl_sampler_n; + } + + // create metal library and function objects + bool shd_valid = true; + if (desc->vertex_func.source || desc->vertex_func.bytecode.ptr) { + shd_valid &= _sg_mtl_create_shader_func(&desc->vertex_func, desc->label, "vs", &shd->mtl.vertex_func); + } + if (desc->fragment_func.source || desc->fragment_func.bytecode.ptr) { + shd_valid &= _sg_mtl_create_shader_func(&desc->fragment_func, desc->label, "fs", &shd->mtl.fragment_func); + } + if (desc->compute_func.source || desc->compute_func.bytecode.ptr) { + shd_valid &= _sg_mtl_create_shader_func(&desc->compute_func, desc->label, "cs", &shd->mtl.compute_func); + } + if (!shd_valid) { + _sg_mtl_discard_shader_func(&shd->mtl.vertex_func); + _sg_mtl_discard_shader_func(&shd->mtl.fragment_func); + _sg_mtl_discard_shader_func(&shd->mtl.compute_func); + } + return shd_valid ? SG_RESOURCESTATE_VALID : SG_RESOURCESTATE_FAILED; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_mtl_discard_shader_func(&shd->mtl.vertex_func); + _sg_mtl_discard_shader_func(&shd->mtl.fragment_func); + _sg_mtl_discard_shader_func(&shd->mtl.compute_func); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && shd && desc); + SOKOL_ASSERT(desc->shader.id == shd->slot.id); + + pip->shader = shd; + + if (pip->cmn.is_compute) { + NSError* err = NULL; + MTLComputePipelineDescriptor* cp_desc = [[MTLComputePipelineDescriptor alloc] init]; + cp_desc.computeFunction = _sg_mtl_id(shd->mtl.compute_func.mtl_func); + cp_desc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT((stage != SG_SHADERSTAGE_VERTEX) && (stage != SG_SHADERSTAGE_FRAGMENT)); + if ((stage == SG_SHADERSTAGE_COMPUTE) && shd->cmn.storage_buffers[i].readonly) { + const NSUInteger mtl_slot = shd->mtl.sbuf_buffer_n[i]; + cp_desc.buffers[mtl_slot].mutability = MTLMutabilityImmutable; + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + cp_desc.label = [NSString stringWithFormat:@"%s", desc->label]; + } + #endif + id mtl_cps = [_sg.mtl.device + newComputePipelineStateWithDescriptor:cp_desc + options:MTLPipelineOptionNone + reflection:nil + error:&err]; + _SG_OBJC_RELEASE(cp_desc); + if (nil == mtl_cps) { + SOKOL_ASSERT(err); + _SG_ERROR(METAL_CREATE_CPS_FAILED); + _SG_LOGMSG(METAL_CREATE_CPS_OUTPUT, [err.localizedDescription UTF8String]); + return SG_RESOURCESTATE_FAILED; + } + pip->mtl.cps = _sg_mtl_add_resource(mtl_cps); + _SG_OBJC_RELEASE(mtl_cps); + pip->mtl.threads_per_threadgroup = shd->mtl.threads_per_threadgroup; + } else { + sg_primitive_type prim_type = desc->primitive_type; + pip->mtl.prim_type = _sg_mtl_primitive_type(prim_type); + pip->mtl.index_size = _sg_mtl_index_size(pip->cmn.index_type); + if (SG_INDEXTYPE_NONE != pip->cmn.index_type) { + pip->mtl.index_type = _sg_mtl_index_type(pip->cmn.index_type); + } + pip->mtl.cull_mode = _sg_mtl_cull_mode(desc->cull_mode); + pip->mtl.winding = _sg_mtl_winding(desc->face_winding); + pip->mtl.stencil_ref = desc->stencil.ref; + + // create vertex-descriptor + MTLVertexDescriptor* vtx_desc = [MTLVertexDescriptor vertexDescriptor]; + for (NSUInteger attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[a_state->buffer_index]); + vtx_desc.attributes[attr_index].format = _sg_mtl_vertex_format(a_state->format); + vtx_desc.attributes[attr_index].offset = (NSUInteger)a_state->offset; + vtx_desc.attributes[attr_index].bufferIndex = _sg_mtl_vertexbuffer_bindslot((size_t)a_state->buffer_index); + } + for (NSUInteger layout_index = 0; layout_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; layout_index++) { + if (pip->cmn.vertex_buffer_layout_active[layout_index]) { + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[layout_index]; + const NSUInteger mtl_vb_slot = _sg_mtl_vertexbuffer_bindslot(layout_index); + SOKOL_ASSERT(l_state->stride > 0); + vtx_desc.layouts[mtl_vb_slot].stride = (NSUInteger)l_state->stride; + vtx_desc.layouts[mtl_vb_slot].stepFunction = _sg_mtl_step_function(l_state->step_func); + vtx_desc.layouts[mtl_vb_slot].stepRate = (NSUInteger)l_state->step_rate; + if (SG_VERTEXSTEP_PER_INSTANCE == l_state->step_func) { + // NOTE: not actually used in _sg_mtl_draw() + pip->cmn.use_instanced_draw = true; + } + } + } + + // render-pipeline descriptor + MTLRenderPipelineDescriptor* rp_desc = [[MTLRenderPipelineDescriptor alloc] init]; + rp_desc.vertexDescriptor = vtx_desc; + SOKOL_ASSERT(shd->mtl.vertex_func.mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.vertex_func.mtl_func); + SOKOL_ASSERT(shd->mtl.fragment_func.mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.fragment_func.mtl_func); + rp_desc.rasterSampleCount = (NSUInteger)desc->sample_count; + rp_desc.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; + rp_desc.alphaToOneEnabled = NO; + rp_desc.rasterizationEnabled = YES; + rp_desc.depthAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format); + if (desc->depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL) { + rp_desc.stencilAttachmentPixelFormat = _sg_mtl_pixel_format(desc->depth.pixel_format); + } + for (NSUInteger i = 0; i < (NSUInteger)desc->color_count; i++) { + SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); + const sg_color_target_state* cs = &desc->colors[i]; + rp_desc.colorAttachments[i].pixelFormat = _sg_mtl_pixel_format(cs->pixel_format); + rp_desc.colorAttachments[i].writeMask = _sg_mtl_color_write_mask(cs->write_mask); + rp_desc.colorAttachments[i].blendingEnabled = cs->blend.enabled; + rp_desc.colorAttachments[i].alphaBlendOperation = _sg_mtl_blend_op(cs->blend.op_alpha); + rp_desc.colorAttachments[i].rgbBlendOperation = _sg_mtl_blend_op(cs->blend.op_rgb); + rp_desc.colorAttachments[i].destinationAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_alpha); + rp_desc.colorAttachments[i].destinationRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.dst_factor_rgb); + rp_desc.colorAttachments[i].sourceAlphaBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_alpha); + rp_desc.colorAttachments[i].sourceRGBBlendFactor = _sg_mtl_blend_factor(cs->blend.src_factor_rgb); + } + // set buffer mutability for all read-only buffers (vertex buffers and read-only storage buffers) + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + if (pip->cmn.vertex_buffer_layout_active[i]) { + const NSUInteger mtl_slot = _sg_mtl_vertexbuffer_bindslot(i); + rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const NSUInteger mtl_slot = shd->mtl.sbuf_buffer_n[i]; + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT(stage != SG_SHADERSTAGE_COMPUTE); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(shd->cmn.storage_buffers[i].readonly); + rp_desc.vertexBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(shd->cmn.storage_buffers[i].readonly); + rp_desc.fragmentBuffers[mtl_slot].mutability = MTLMutabilityImmutable; + } + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + rp_desc.label = [NSString stringWithFormat:@"%s", desc->label]; + } + #endif + NSError* err = NULL; + id mtl_rps = [_sg.mtl.device newRenderPipelineStateWithDescriptor:rp_desc error:&err]; + _SG_OBJC_RELEASE(rp_desc); + if (nil == mtl_rps) { + SOKOL_ASSERT(err); + _SG_ERROR(METAL_CREATE_RPS_FAILED); + _SG_LOGMSG(METAL_CREATE_RPS_OUTPUT, [err.localizedDescription UTF8String]); + return SG_RESOURCESTATE_FAILED; + } + pip->mtl.rps = _sg_mtl_add_resource(mtl_rps); + _SG_OBJC_RELEASE(mtl_rps); + + // depth-stencil-state + MTLDepthStencilDescriptor* ds_desc = [[MTLDepthStencilDescriptor alloc] init]; + ds_desc.depthCompareFunction = _sg_mtl_compare_func(desc->depth.compare); + ds_desc.depthWriteEnabled = desc->depth.write_enabled; + if (desc->stencil.enabled) { + const sg_stencil_face_state* sb = &desc->stencil.back; + ds_desc.backFaceStencil = [[MTLStencilDescriptor alloc] init]; + ds_desc.backFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sb->fail_op); + ds_desc.backFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sb->depth_fail_op); + ds_desc.backFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sb->pass_op); + ds_desc.backFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sb->compare); + ds_desc.backFaceStencil.readMask = desc->stencil.read_mask; + ds_desc.backFaceStencil.writeMask = desc->stencil.write_mask; + const sg_stencil_face_state* sf = &desc->stencil.front; + ds_desc.frontFaceStencil = [[MTLStencilDescriptor alloc] init]; + ds_desc.frontFaceStencil.stencilFailureOperation = _sg_mtl_stencil_op(sf->fail_op); + ds_desc.frontFaceStencil.depthFailureOperation = _sg_mtl_stencil_op(sf->depth_fail_op); + ds_desc.frontFaceStencil.depthStencilPassOperation = _sg_mtl_stencil_op(sf->pass_op); + ds_desc.frontFaceStencil.stencilCompareFunction = _sg_mtl_compare_func(sf->compare); + ds_desc.frontFaceStencil.readMask = desc->stencil.read_mask; + ds_desc.frontFaceStencil.writeMask = desc->stencil.write_mask; + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + ds_desc.label = [NSString stringWithFormat:@"%s.dss", desc->label]; + } + #endif + id mtl_dss = [_sg.mtl.device newDepthStencilStateWithDescriptor:ds_desc]; + _SG_OBJC_RELEASE(ds_desc); + if (nil == mtl_dss) { + _SG_ERROR(METAL_CREATE_DSS_FAILED); + return SG_RESOURCESTATE_FAILED; + } + pip->mtl.dss = _sg_mtl_add_resource(mtl_dss); + _SG_OBJC_RELEASE(mtl_dss); + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + // it's valid to call release resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, pip->mtl.cps); + _sg_mtl_release_resource(_sg.frame_index, pip->mtl.rps); + _sg_mtl_release_resource(_sg.frame_index, pip->mtl.dss); +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_attachments(_sg_attachments_t* atts, _sg_image_t** color_images, _sg_image_t** resolve_images, _sg_image_t* ds_img, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + SOKOL_ASSERT(color_images && resolve_images); + + // copy image pointers + for (int i = 0; i < atts->cmn.num_colors; i++) { + const sg_attachment_desc* color_desc = &desc->colors[i]; + _SOKOL_UNUSED(color_desc); + SOKOL_ASSERT(color_desc->image.id != SG_INVALID_ID); + SOKOL_ASSERT(0 == atts->mtl.colors[i].image); + SOKOL_ASSERT(color_images[i] && (color_images[i]->slot.id == color_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(color_images[i]->cmn.pixel_format)); + atts->mtl.colors[i].image = color_images[i]; + + const sg_attachment_desc* resolve_desc = &desc->resolves[i]; + if (resolve_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(0 == atts->mtl.resolves[i].image); + SOKOL_ASSERT(resolve_images[i] && (resolve_images[i]->slot.id == resolve_desc->image.id)); + SOKOL_ASSERT(color_images[i] && (color_images[i]->cmn.pixel_format == resolve_images[i]->cmn.pixel_format)); + atts->mtl.resolves[i].image = resolve_images[i]; + } + } + SOKOL_ASSERT(0 == atts->mtl.depth_stencil.image); + const sg_attachment_desc* ds_desc = &desc->depth_stencil; + if (ds_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(ds_img && (ds_img->slot.id == ds_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(ds_img->cmn.pixel_format)); + atts->mtl.depth_stencil.image = ds_img; + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _SOKOL_UNUSED(atts); +} + +_SOKOL_PRIVATE _sg_image_t* _sg_mtl_attachments_color_image(const _sg_attachments_t* atts, int index) { + // NOTE: may return null + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->mtl.colors[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_mtl_attachments_resolve_image(const _sg_attachments_t* atts, int index) { + // NOTE: may return null + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + return atts->mtl.resolves[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_mtl_attachments_ds_image(const _sg_attachments_t* atts) { + // NOTE: may return null + SOKOL_ASSERT(atts); + return atts->mtl.depth_stencil.image; +} + +_SOKOL_PRIVATE void _sg_mtl_bind_uniform_buffers(void) { + // In the Metal backend, uniform buffer bindings happen once in sg_begin_pass() and + // remain valid for the entire pass. Only binding offsets will be updated + // in sg_apply_uniforms() + if (_sg.cur_pass.is_compute) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + for (size_t slot = 0; slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS; slot++) { + [_sg.mtl.compute_cmd_encoder + setBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:slot]; + } + } else { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + for (size_t slot = 0; slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS; slot++) { + [_sg.mtl.render_cmd_encoder + setVertexBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:slot]; + [_sg.mtl.render_cmd_encoder + setFragmentBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:slot]; + } + } +} + +_SOKOL_PRIVATE void _sg_mtl_begin_compute_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); (void)pass; + SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + + // NOTE: we actually want computeCommandEncoderWithDispatchType:MTLDispatchTypeConcurrent, but + // that requires bumping the macOS base version to 10.14 + _sg.mtl.compute_cmd_encoder = [_sg.mtl.cmd_buffer computeCommandEncoder]; + if (nil == _sg.mtl.compute_cmd_encoder) { + _sg.cur_pass.valid = false; + return; + } + + #if defined(SOKOL_DEBUG) + if (pass->label) { + _sg.mtl.compute_cmd_encoder.label = [NSString stringWithUTF8String:pass->label]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_begin_render_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + + const _sg_attachments_t* atts = _sg.cur_pass.atts; + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + MTLRenderPassDescriptor* pass_desc = [MTLRenderPassDescriptor renderPassDescriptor]; + SOKOL_ASSERT(pass_desc); + if (atts) { + // setup pass descriptor for offscreen rendering + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_VALID); + for (NSUInteger i = 0; i < (NSUInteger)atts->cmn.num_colors; i++) { + const _sg_attachment_common_t* cmn_color_att = &atts->cmn.colors[i]; + const _sg_mtl_attachment_t* mtl_color_att = &atts->mtl.colors[i]; + const _sg_image_t* color_att_img = mtl_color_att->image; + const _sg_attachment_common_t* cmn_resolve_att = &atts->cmn.resolves[i]; + const _sg_mtl_attachment_t* mtl_resolve_att = &atts->mtl.resolves[i]; + const _sg_image_t* resolve_att_img = mtl_resolve_att->image; + SOKOL_ASSERT(color_att_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(color_att_img->slot.id == cmn_color_att->image_id.id); + SOKOL_ASSERT(color_att_img->mtl.tex[color_att_img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.colorAttachments[i].loadAction = _sg_mtl_load_action(action->colors[i].load_action); + pass_desc.colorAttachments[i].storeAction = _sg_mtl_store_action(action->colors[i].store_action, resolve_att_img != 0); + sg_color c = action->colors[i].clear_value; + pass_desc.colorAttachments[i].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a); + pass_desc.colorAttachments[i].texture = _sg_mtl_id(color_att_img->mtl.tex[color_att_img->cmn.active_slot]); + pass_desc.colorAttachments[i].level = (NSUInteger)cmn_color_att->mip_level; + switch (color_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.colorAttachments[i].slice = (NSUInteger)cmn_color_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.colorAttachments[i].depthPlane = (NSUInteger)cmn_color_att->slice; + break; + default: break; + } + if (resolve_att_img) { + SOKOL_ASSERT(resolve_att_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(resolve_att_img->slot.id == cmn_resolve_att->image_id.id); + SOKOL_ASSERT(resolve_att_img->mtl.tex[resolve_att_img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.colorAttachments[i].resolveTexture = _sg_mtl_id(resolve_att_img->mtl.tex[resolve_att_img->cmn.active_slot]); + pass_desc.colorAttachments[i].resolveLevel = (NSUInteger)cmn_resolve_att->mip_level; + switch (resolve_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.colorAttachments[i].resolveSlice = (NSUInteger)cmn_resolve_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.colorAttachments[i].resolveDepthPlane = (NSUInteger)cmn_resolve_att->slice; + break; + default: break; + } + } + } + const _sg_image_t* ds_att_img = atts->mtl.depth_stencil.image; + if (0 != ds_att_img) { + SOKOL_ASSERT(ds_att_img->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(ds_att_img->slot.id == atts->cmn.depth_stencil.image_id.id); + SOKOL_ASSERT(ds_att_img->mtl.tex[ds_att_img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + pass_desc.depthAttachment.texture = _sg_mtl_id(ds_att_img->mtl.tex[ds_att_img->cmn.active_slot]); + pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.load_action); + pass_desc.depthAttachment.storeAction = _sg_mtl_store_action(action->depth.store_action, false); + pass_desc.depthAttachment.clearDepth = action->depth.clear_value; + const _sg_attachment_common_t* cmn_ds_att = &atts->cmn.depth_stencil; + switch (ds_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.depthAttachment.slice = (NSUInteger)cmn_ds_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.depthAttachment.resolveDepthPlane = (NSUInteger)cmn_ds_att->slice; + break; + default: break; + } + if (_sg_is_depth_stencil_format(ds_att_img->cmn.pixel_format)) { + pass_desc.stencilAttachment.texture = _sg_mtl_id(ds_att_img->mtl.tex[ds_att_img->cmn.active_slot]); + pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.load_action); + pass_desc.stencilAttachment.storeAction = _sg_mtl_store_action(action->depth.store_action, false); + pass_desc.stencilAttachment.clearStencil = action->stencil.clear_value; + switch (ds_att_img->cmn.type) { + case SG_IMAGETYPE_CUBE: + case SG_IMAGETYPE_ARRAY: + pass_desc.stencilAttachment.slice = (NSUInteger)cmn_ds_att->slice; + break; + case SG_IMAGETYPE_3D: + pass_desc.stencilAttachment.resolveDepthPlane = (NSUInteger)cmn_ds_att->slice; + break; + default: break; + } + } + } + } else { + // setup pass descriptor for swapchain rendering + // + // NOTE: at least in macOS Sonoma this no longer seems to be the case, the + // current drawable is also valid in a minimized window + // === + // an MTKView current_drawable will not be valid if window is minimized, don't do any rendering in this case + if (0 == swapchain->metal.current_drawable) { + _sg.cur_pass.valid = false; + return; + } + // pin the swapchain resources into memory so that they outlive their command buffer + // (this is necessary because the command buffer doesn't retain references) + int pass_desc_ref = _sg_mtl_add_resource(pass_desc); + _sg_mtl_release_resource(_sg.frame_index, pass_desc_ref); + + _sg.mtl.cur_drawable = (__bridge id) swapchain->metal.current_drawable; + if (swapchain->sample_count > 1) { + // multi-sampling: render into msaa texture, resolve into drawable texture + id msaa_tex = (__bridge id) swapchain->metal.msaa_color_texture; + SOKOL_ASSERT(msaa_tex != nil); + pass_desc.colorAttachments[0].texture = msaa_tex; + pass_desc.colorAttachments[0].resolveTexture = _sg.mtl.cur_drawable.texture; + pass_desc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve; + } else { + // non-msaa: render into current_drawable + pass_desc.colorAttachments[0].texture = _sg.mtl.cur_drawable.texture; + pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore; + } + pass_desc.colorAttachments[0].loadAction = _sg_mtl_load_action(action->colors[0].load_action); + const sg_color c = action->colors[0].clear_value; + pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(c.r, c.g, c.b, c.a); + + // optional depth-stencil texture + if (swapchain->metal.depth_stencil_texture) { + id ds_tex = (__bridge id) swapchain->metal.depth_stencil_texture; + SOKOL_ASSERT(ds_tex != nil); + pass_desc.depthAttachment.texture = ds_tex; + pass_desc.depthAttachment.storeAction = MTLStoreActionDontCare; + pass_desc.depthAttachment.loadAction = _sg_mtl_load_action(action->depth.load_action); + pass_desc.depthAttachment.clearDepth = action->depth.clear_value; + if (_sg_is_depth_stencil_format(swapchain->depth_format)) { + pass_desc.stencilAttachment.texture = ds_tex; + pass_desc.stencilAttachment.storeAction = MTLStoreActionDontCare; + pass_desc.stencilAttachment.loadAction = _sg_mtl_load_action(action->stencil.load_action); + pass_desc.stencilAttachment.clearStencil = action->stencil.clear_value; + } + } + } + + // NOTE: at least in macOS Sonoma, the following is no longer the case, a valid + // render command encoder is also returned in a minimized window + // === + // create a render command encoder, this might return nil if window is minimized + _sg.mtl.render_cmd_encoder = [_sg.mtl.cmd_buffer renderCommandEncoderWithDescriptor:pass_desc]; + if (nil == _sg.mtl.render_cmd_encoder) { + _sg.cur_pass.valid = false; + return; + } + + #if defined(SOKOL_DEBUG) + if (pass->label) { + _sg.mtl.render_cmd_encoder.label = [NSString stringWithUTF8String:pass->label]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + SOKOL_ASSERT(_sg.mtl.cmd_queue); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.cur_drawable); + _sg_mtl_clear_state_cache(); + + // if this is the first pass in the frame, create one command buffer and blit-cmd-encoder for the entire frame + if (nil == _sg.mtl.cmd_buffer) { + // block until the oldest frame in flight has finished + dispatch_semaphore_wait(_sg.mtl.sem, DISPATCH_TIME_FOREVER); + if (_sg.desc.mtl_use_command_buffer_with_retained_references) { + _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBuffer]; + } else { + _sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBufferWithUnretainedReferences]; + } + [_sg.mtl.cmd_buffer enqueue]; + [_sg.mtl.cmd_buffer addCompletedHandler:^(id cmd_buf) { + // NOTE: this code is called on a different thread! + _SOKOL_UNUSED(cmd_buf); + dispatch_semaphore_signal(_sg.mtl.sem); + }]; + } + + // if this is first pass in frame, get uniform buffer base pointer + if (0 == _sg.mtl.cur_ub_base_ptr) { + _sg.mtl.cur_ub_base_ptr = (uint8_t*)[_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] contents]; + } + + if (pass->compute) { + _sg_mtl_begin_compute_pass(pass); + } else { + _sg_mtl_begin_render_pass(pass); + } + + // bind uniform buffers, those bindings remain valid for the entire pass + if (_sg.cur_pass.valid) { + _sg_mtl_bind_uniform_buffers(); + } +} + +_SOKOL_PRIVATE void _sg_mtl_end_pass(void) { + if (nil != _sg.mtl.render_cmd_encoder) { + [_sg.mtl.render_cmd_encoder endEncoding]; + // NOTE: MTLRenderCommandEncoder is autoreleased + _sg.mtl.render_cmd_encoder = nil; + } + if (nil != _sg.mtl.compute_cmd_encoder) { + [_sg.mtl.compute_cmd_encoder endEncoding]; + // NOTE: MTLComputeCommandEncoder is autoreleased + _sg.mtl.compute_cmd_encoder = nil; + + // synchronize any managed buffers written by the GPU + #if defined(_SG_TARGET_MACOS) + if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) { + if (_sg.compute.readwrite_sbufs.cur > 0) { + id blit_cmd_encoder = [_sg.mtl.cmd_buffer blitCommandEncoder]; + for (uint32_t i = 0; i < _sg.compute.readwrite_sbufs.cur; i++) { + _sg_buffer_t* sbuf = _sg_lookup_buffer(&_sg.pools, _sg.compute.readwrite_sbufs.items[i]); + if (sbuf) { + [blit_cmd_encoder synchronizeResource:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot])]; + } + } + [blit_cmd_encoder endEncoding]; + } + } + #endif + } + // if this is a swapchain pass, present the drawable + if (nil != _sg.mtl.cur_drawable) { + [_sg.mtl.cmd_buffer presentDrawable:_sg.mtl.cur_drawable]; + _sg.mtl.cur_drawable = nil; + } +} + +_SOKOL_PRIVATE void _sg_mtl_commit(void) { + SOKOL_ASSERT(nil == _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(nil != _sg.mtl.cmd_buffer); + + // commit the frame's command buffer + [_sg.mtl.cmd_buffer commit]; + + // garbage-collect resources pending for release + _sg_mtl_garbage_collect(_sg.frame_index); + + // rotate uniform buffer slot + if (++_sg.mtl.cur_frame_rotate_index >= SG_NUM_INFLIGHT_FRAMES) { + _sg.mtl.cur_frame_rotate_index = 0; + } + _sg.mtl.cur_ub_offset = 0; + _sg.mtl.cur_ub_base_ptr = 0; + // NOTE: MTLCommandBuffer is autoreleased + _sg.mtl.cmd_buffer = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(_sg.cur_pass.height > 0); + MTLViewport vp; + vp.originX = (double) x; + vp.originY = (double) (origin_top_left ? y : (_sg.cur_pass.height - (y + h))); + vp.width = (double) w; + vp.height = (double) h; + vp.znear = 0.0; + vp.zfar = 1.0; + [_sg.mtl.render_cmd_encoder setViewport:vp]; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(_sg.cur_pass.width > 0); + SOKOL_ASSERT(_sg.cur_pass.height > 0); + // clip against framebuffer rect + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.width, _sg.cur_pass.height); + MTLScissorRect r; + r.x = (NSUInteger)clip.x; + r.y = (NSUInteger) (origin_top_left ? clip.y : (_sg.cur_pass.height - (clip.y + clip.h))); + r.width = (NSUInteger)clip.w; + r.height = (NSUInteger)clip.h; + [_sg.mtl.render_cmd_encoder setScissorRect:r]; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + if (_sg.mtl.state_cache.cur_pipeline_id.id != pip->slot.id) { + _sg.mtl.state_cache.cur_pipeline = pip; + _sg.mtl.state_cache.cur_pipeline_id.id = pip->slot.id; + if (pip->cmn.is_compute) { + SOKOL_ASSERT(_sg.cur_pass.is_compute); + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(pip->mtl.cps != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.compute_cmd_encoder setComputePipelineState:_sg_mtl_id(pip->mtl.cps)]; + } else { + SOKOL_ASSERT(!_sg.cur_pass.is_compute); + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + sg_color c = pip->cmn.blend_color; + [_sg.mtl.render_cmd_encoder setBlendColorRed:c.r green:c.g blue:c.b alpha:c.a]; + _sg_stats_add(metal.pipeline.num_set_blend_color, 1); + [_sg.mtl.render_cmd_encoder setCullMode:pip->mtl.cull_mode]; + _sg_stats_add(metal.pipeline.num_set_cull_mode, 1); + [_sg.mtl.render_cmd_encoder setFrontFacingWinding:pip->mtl.winding]; + _sg_stats_add(metal.pipeline.num_set_front_facing_winding, 1); + [_sg.mtl.render_cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref]; + _sg_stats_add(metal.pipeline.num_set_stencil_reference_value, 1); + [_sg.mtl.render_cmd_encoder setDepthBias:pip->cmn.depth.bias slopeScale:pip->cmn.depth.bias_slope_scale clamp:pip->cmn.depth.bias_clamp]; + _sg_stats_add(metal.pipeline.num_set_depth_bias, 1); + SOKOL_ASSERT(pip->mtl.rps != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.render_cmd_encoder setRenderPipelineState:_sg_mtl_id(pip->mtl.rps)]; + _sg_stats_add(metal.pipeline.num_set_render_pipeline_state, 1); + SOKOL_ASSERT(pip->mtl.dss != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.render_cmd_encoder setDepthStencilState:_sg_mtl_id(pip->mtl.dss)]; + _sg_stats_add(metal.pipeline.num_set_depth_stencil_state, 1); + } + } +} + +_SOKOL_PRIVATE bool _sg_mtl_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + SOKOL_ASSERT(bnd->pip && bnd->pip->shader); + SOKOL_ASSERT(bnd->pip->shader->slot.id == bnd->pip->cmn.shader_id.id); + const _sg_shader_t* shd = bnd->pip->shader; + + // don't set vertex- and index-buffers in compute passes + if (!_sg.cur_pass.is_compute) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + // store index buffer binding, this will be needed later in sg_draw() + _sg.mtl.state_cache.cur_indexbuffer = bnd->ib; + _sg.mtl.state_cache.cur_indexbuffer_offset = bnd->ib_offset; + if (bnd->ib) { + SOKOL_ASSERT(bnd->pip->cmn.index_type != SG_INDEXTYPE_NONE); + _sg.mtl.state_cache.cur_indexbuffer_id.id = bnd->ib->slot.id; + } else { + SOKOL_ASSERT(bnd->pip->cmn.index_type == SG_INDEXTYPE_NONE); + _sg.mtl.state_cache.cur_indexbuffer_id.id = SG_INVALID_ID; + } + // apply vertex buffers + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* vb = bnd->vbs[i]; + if (vb == 0) { + continue; + } + const NSUInteger mtl_slot = _sg_mtl_vertexbuffer_bindslot(i); + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_BUFFER_BINDINGS); + const int vb_offset = bnd->vb_offsets[i]; + if ((_sg.mtl.state_cache.cur_vs_buffer_ids[mtl_slot].id != vb->slot.id) || + (_sg.mtl.state_cache.cur_vs_buffer_offsets[mtl_slot] != vb_offset)) + { + _sg.mtl.state_cache.cur_vs_buffer_offsets[mtl_slot] = vb_offset; + if (_sg.mtl.state_cache.cur_vs_buffer_ids[mtl_slot].id != vb->slot.id) { + // vertex buffer has changed + _sg.mtl.state_cache.cur_vs_buffer_ids[mtl_slot].id = vb->slot.id; + SOKOL_ASSERT(vb->mtl.buf[vb->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(vb->mtl.buf[vb->cmn.active_slot]) + offset:(NSUInteger)vb_offset + atIndex:mtl_slot]; + } else { + // only vertex buffer offset has changed + [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)vb_offset atIndex:mtl_slot]; + } + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } + } + + // apply image bindings + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + const _sg_image_t* img = bnd->imgs[i]; + if (img == 0) { + continue; + } + SOKOL_ASSERT(img->mtl.tex[img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const sg_shader_stage stage = shd->cmn.images[i].stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE)); + const NSUInteger mtl_slot = shd->mtl.img_texture_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_IMAGE_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (_sg.mtl.state_cache.cur_vs_image_ids[mtl_slot].id != img->slot.id) { + _sg.mtl.state_cache.cur_vs_image_ids[mtl_slot].id = img->slot.id; + [_sg.mtl.render_cmd_encoder setVertexTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_texture, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (_sg.mtl.state_cache.cur_fs_image_ids[mtl_slot].id != img->slot.id) { + _sg.mtl.state_cache.cur_fs_image_ids[mtl_slot].id = img->slot.id; + [_sg.mtl.render_cmd_encoder setFragmentTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_texture, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (_sg.mtl.state_cache.cur_cs_image_ids[mtl_slot].id != img->slot.id) { + _sg.mtl.state_cache.cur_cs_image_ids[mtl_slot].id = img->slot.id; + [_sg.mtl.compute_cmd_encoder setTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_texture, 1); + } + } + } + + // apply sampler bindings + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + const _sg_sampler_t* smp = bnd->smps[i]; + if (smp == 0) { + continue; + } + SOKOL_ASSERT(smp->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX); + const sg_shader_stage stage = shd->cmn.samplers[i].stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE)); + const NSUInteger mtl_slot = shd->mtl.smp_sampler_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (_sg.mtl.state_cache.cur_vs_sampler_ids[mtl_slot].id != smp->slot.id) { + _sg.mtl.state_cache.cur_vs_sampler_ids[mtl_slot].id = smp->slot.id; + [_sg.mtl.render_cmd_encoder setVertexSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_sampler_state, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (_sg.mtl.state_cache.cur_fs_sampler_ids[mtl_slot].id != smp->slot.id) { + _sg.mtl.state_cache.cur_fs_sampler_ids[mtl_slot].id = smp->slot.id; + [_sg.mtl.render_cmd_encoder setFragmentSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_sampler_state, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (_sg.mtl.state_cache.cur_cs_sampler_ids[mtl_slot].id != smp->slot.id) { + _sg.mtl.state_cache.cur_cs_sampler_ids[mtl_slot].id = smp->slot.id; + [_sg.mtl.compute_cmd_encoder setSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_sampler_state, 1); + } + } + } + + // apply storage buffer bindings + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + const _sg_buffer_t* sbuf = bnd->sbufs[i]; + if (sbuf == 0) { + continue; + } + SOKOL_ASSERT(sbuf->mtl.buf[sbuf->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const sg_shader_stage stage = shd->cmn.storage_buffers[i].stage; + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VERTEX) || (stage == SG_SHADERSTAGE_FRAGMENT) || (stage == SG_SHADERSTAGE_COMPUTE)); + const NSUInteger mtl_slot = shd->mtl.sbuf_buffer_n[i]; + SOKOL_ASSERT(mtl_slot < _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (_sg.mtl.state_cache.cur_vs_buffer_ids[mtl_slot].id != sbuf->slot.id) { + _sg.mtl.state_cache.cur_vs_buffer_ids[mtl_slot].id = sbuf->slot.id; + [_sg.mtl.render_cmd_encoder setVertexBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:0 atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + if (_sg.mtl.state_cache.cur_fs_buffer_ids[mtl_slot].id != sbuf->slot.id) { + _sg.mtl.state_cache.cur_fs_buffer_ids[mtl_slot].id = sbuf->slot.id; + [_sg.mtl.render_cmd_encoder setFragmentBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:0 atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_fragment_buffer, 1); + } + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + if (_sg.mtl.state_cache.cur_cs_buffer_ids[mtl_slot].id != sbuf->slot.id) { + _sg.mtl.state_cache.cur_cs_buffer_ids[mtl_slot].id = sbuf->slot.id; + [_sg.mtl.compute_cmd_encoder setBuffer:_sg_mtl_id(sbuf->mtl.buf[sbuf->cmn.active_slot]) offset:0 atIndex:mtl_slot]; + _sg_stats_add(metal.bindings.num_set_compute_buffer, 1); + } + } + } + return true; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT(((size_t)_sg.mtl.cur_ub_offset + data->size) <= (size_t)_sg.mtl.ub_size); + SOKOL_ASSERT((_sg.mtl.cur_ub_offset & (_SG_MTL_UB_ALIGN-1)) == 0); + const _sg_pipeline_t* pip = _sg.mtl.state_cache.cur_pipeline; + SOKOL_ASSERT(pip && pip->shader); + SOKOL_ASSERT(pip->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id); + const _sg_shader_t* shd = pip->shader; + SOKOL_ASSERT(shd->slot.id == pip->cmn.shader_id.id); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + + const sg_shader_stage stage = shd->cmn.uniform_blocks[ub_slot].stage; + const NSUInteger mtl_slot = shd->mtl.ub_buffer_n[ub_slot]; + + // copy to global uniform buffer, record offset into cmd encoder, and advance offset + uint8_t* dst = &_sg.mtl.cur_ub_base_ptr[_sg.mtl.cur_ub_offset]; + memcpy(dst, data->ptr, data->size); + if (stage == SG_SHADERSTAGE_VERTEX) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + [_sg.mtl.render_cmd_encoder setVertexBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot]; + _sg_stats_add(metal.uniforms.num_set_vertex_buffer_offset, 1); + } else if (stage == SG_SHADERSTAGE_FRAGMENT) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + [_sg.mtl.render_cmd_encoder setFragmentBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot]; + _sg_stats_add(metal.uniforms.num_set_fragment_buffer_offset, 1); + } else if (stage == SG_SHADERSTAGE_COMPUTE) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + [_sg.mtl.compute_cmd_encoder setBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:mtl_slot]; + _sg_stats_add(metal.uniforms.num_set_compute_buffer_offset, 1); + } else { + SOKOL_UNREACHABLE; + } + _sg.mtl.cur_ub_offset = _sg_roundup(_sg.mtl.cur_ub_offset + (int)data->size, _SG_MTL_UB_ALIGN); +} + +_SOKOL_PRIVATE void _sg_mtl_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(nil != _sg.mtl.render_cmd_encoder); + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline && (_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id)); + if (SG_INDEXTYPE_NONE != _sg.mtl.state_cache.cur_pipeline->cmn.index_type) { + // indexed rendering + SOKOL_ASSERT(_sg.mtl.state_cache.cur_indexbuffer && (_sg.mtl.state_cache.cur_indexbuffer->slot.id == _sg.mtl.state_cache.cur_indexbuffer_id.id)); + const _sg_buffer_t* ib = _sg.mtl.state_cache.cur_indexbuffer; + SOKOL_ASSERT(ib->mtl.buf[ib->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + const NSUInteger index_buffer_offset = (NSUInteger) (_sg.mtl.state_cache.cur_indexbuffer_offset + base_element * _sg.mtl.state_cache.cur_pipeline->mtl.index_size); + [_sg.mtl.render_cmd_encoder drawIndexedPrimitives:_sg.mtl.state_cache.cur_pipeline->mtl.prim_type + indexCount:(NSUInteger)num_elements + indexType:_sg.mtl.state_cache.cur_pipeline->mtl.index_type + indexBuffer:_sg_mtl_id(ib->mtl.buf[ib->cmn.active_slot]) + indexBufferOffset:index_buffer_offset + instanceCount:(NSUInteger)num_instances]; + } else { + // non-indexed rendering + [_sg.mtl.render_cmd_encoder drawPrimitives:_sg.mtl.state_cache.cur_pipeline->mtl.prim_type + vertexStart:(NSUInteger)base_element + vertexCount:(NSUInteger)num_elements + instanceCount:(NSUInteger)num_instances]; + } +} + +_SOKOL_PRIVATE void _sg_mtl_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + SOKOL_ASSERT(nil != _sg.mtl.compute_cmd_encoder); + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline && (_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id)); + const _sg_pipeline_t* cur_pip = _sg.mtl.state_cache.cur_pipeline; + const MTLSize thread_groups = MTLSizeMake( + (NSUInteger)num_groups_x, + (NSUInteger)num_groups_y, + (NSUInteger)num_groups_z); + const MTLSize threads_per_threadgroup = cur_pip->mtl.threads_per_threadgroup; + [_sg.mtl.compute_cmd_encoder dispatchThreadgroups:thread_groups threadsPerThreadgroup:threads_per_threadgroup]; +} + +_SOKOL_PRIVATE void _sg_mtl_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + __unsafe_unretained id mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]); + void* dst_ptr = [mtl_buf contents]; + memcpy(dst_ptr, data->ptr, data->size); + #if defined(_SG_TARGET_MACOS) + if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) { + [mtl_buf didModifyRange:NSMakeRange(0, data->size)]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(buf && data && data->ptr && (data->size > 0)); + if (new_frame) { + if (++buf->cmn.active_slot >= buf->cmn.num_slots) { + buf->cmn.active_slot = 0; + } + } + __unsafe_unretained id mtl_buf = _sg_mtl_id(buf->mtl.buf[buf->cmn.active_slot]); + uint8_t* dst_ptr = (uint8_t*) [mtl_buf contents]; + dst_ptr += buf->cmn.append_pos; + memcpy(dst_ptr, data->ptr, data->size); + #if defined(_SG_TARGET_MACOS) + if (_sg_mtl_resource_options_storage_mode_managed_or_shared() == MTLResourceStorageModeManaged) { + [mtl_buf didModifyRange:NSMakeRange((NSUInteger)buf->cmn.append_pos, (NSUInteger)data->size)]; + } + #endif +} + +_SOKOL_PRIVATE void _sg_mtl_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + if (++img->cmn.active_slot >= img->cmn.num_slots) { + img->cmn.active_slot = 0; + } + __unsafe_unretained id mtl_tex = _sg_mtl_id(img->mtl.tex[img->cmn.active_slot]); + _sg_mtl_copy_image_data(img, mtl_tex, data); +} + +_SOKOL_PRIVATE void _sg_mtl_push_debug_group(const char* name) { + SOKOL_ASSERT(name); + if (_sg.mtl.render_cmd_encoder) { + [_sg.mtl.render_cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]]; + } else if (_sg.mtl.compute_cmd_encoder) { + [_sg.mtl.compute_cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]]; + } +} + +_SOKOL_PRIVATE void _sg_mtl_pop_debug_group(void) { + if (_sg.mtl.render_cmd_encoder) { + [_sg.mtl.render_cmd_encoder popDebugGroup]; + } else if (_sg.mtl.compute_cmd_encoder) { + [_sg.mtl.compute_cmd_encoder popDebugGroup]; + } +} + +// ██ ██ ███████ ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ █ ██ █████ ██████ ██ ███ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███ ███ ███████ ██████ ██████ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>webgpu +// >>wgpu +#elif defined(SOKOL_WGPU) + +#if !defined(__EMSCRIPTEN__) +// FIXME: webgpu.h differences between Dawn and Emscripten webgpu.h +#define wgpuBufferReference wgpuBufferAddRef +#define wgpuTextureReference wgpuTextureAddRef +#define wgpuTextureViewReference wgpuTextureViewAddRef +#define wgpuSamplerReference wgpuSamplerAddRef +#define WGPUSType_ShaderModuleWGSLDescriptor WGPUSType_ShaderSourceWGSL +_SOKOL_PRIVATE WGPUStringView _sg_wgpu_stringview(const char* str) { + WGPUStringView res; + if (str) { + res.data = str; + res.length = strlen(str); + } else { + res.data = 0; + res.length = 0; + } + return res; +} +_SOKOL_PRIVATE WGPUOptionalBool _sg_wgpu_optional_bool(bool b) { + return b ? WGPUOptionalBool_True : WGPUOptionalBool_False; +} +#else +#define _sg_wgpu_stringview(str) str +#define _sg_wgpu_optional_bool(b) (b) +#endif + +_SOKOL_PRIVATE WGPUBufferUsage _sg_wgpu_buffer_usage(sg_buffer_type t, sg_usage u) { + // FIXME: change to WGPUBufferUsage once Emscripten and Dawn webgpu.h agree + int res = 0; + if (SG_BUFFERTYPE_VERTEXBUFFER == t) { + res = WGPUBufferUsage_Vertex; + } else if (SG_BUFFERTYPE_STORAGEBUFFER == t) { + res = WGPUBufferUsage_Storage; + } else { + res = WGPUBufferUsage_Index; + } + if (SG_USAGE_IMMUTABLE != u) { + res |= WGPUBufferUsage_CopyDst; + } + return (WGPUBufferUsage)res; +} + +_SOKOL_PRIVATE WGPULoadOp _sg_wgpu_load_op(WGPUTextureView view, sg_load_action a) { + if (0 == view) { + return WGPULoadOp_Undefined; + } else switch (a) { + case SG_LOADACTION_CLEAR: + case SG_LOADACTION_DONTCARE: + return WGPULoadOp_Clear; + case SG_LOADACTION_LOAD: + return WGPULoadOp_Load; + default: + SOKOL_UNREACHABLE; + return WGPULoadOp_Force32; + } +} + +_SOKOL_PRIVATE WGPUStoreOp _sg_wgpu_store_op(WGPUTextureView view, sg_store_action a) { + if (0 == view) { + return WGPUStoreOp_Undefined; + } else switch (a) { + case SG_STOREACTION_STORE: + return WGPUStoreOp_Store; + case SG_STOREACTION_DONTCARE: + return WGPUStoreOp_Discard; + default: + SOKOL_UNREACHABLE; + return WGPUStoreOp_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureViewDimension _sg_wgpu_texture_view_dimension(sg_image_type t) { + switch (t) { + case SG_IMAGETYPE_2D: return WGPUTextureViewDimension_2D; + case SG_IMAGETYPE_CUBE: return WGPUTextureViewDimension_Cube; + case SG_IMAGETYPE_3D: return WGPUTextureViewDimension_3D; + case SG_IMAGETYPE_ARRAY: return WGPUTextureViewDimension_2DArray; + default: SOKOL_UNREACHABLE; return WGPUTextureViewDimension_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureDimension _sg_wgpu_texture_dimension(sg_image_type t) { + if (SG_IMAGETYPE_3D == t) { + return WGPUTextureDimension_3D; + } else { + return WGPUTextureDimension_2D; + } +} + +_SOKOL_PRIVATE WGPUTextureSampleType _sg_wgpu_texture_sample_type(sg_image_sample_type t, bool msaa) { + switch (t) { + case SG_IMAGESAMPLETYPE_FLOAT: return msaa ? WGPUTextureSampleType_UnfilterableFloat : WGPUTextureSampleType_Float; + case SG_IMAGESAMPLETYPE_DEPTH: return WGPUTextureSampleType_Depth; + case SG_IMAGESAMPLETYPE_SINT: return WGPUTextureSampleType_Sint; + case SG_IMAGESAMPLETYPE_UINT: return WGPUTextureSampleType_Uint; + case SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT: return WGPUTextureSampleType_UnfilterableFloat; + default: SOKOL_UNREACHABLE; return WGPUTextureSampleType_Force32; + } +} + +_SOKOL_PRIVATE WGPUSamplerBindingType _sg_wgpu_sampler_binding_type(sg_sampler_type t) { + switch (t) { + case SG_SAMPLERTYPE_FILTERING: return WGPUSamplerBindingType_Filtering; + case SG_SAMPLERTYPE_COMPARISON: return WGPUSamplerBindingType_Comparison; + case SG_SAMPLERTYPE_NONFILTERING: return WGPUSamplerBindingType_NonFiltering; + default: SOKOL_UNREACHABLE; return WGPUSamplerBindingType_Force32; + } +} + +_SOKOL_PRIVATE WGPUAddressMode _sg_wgpu_sampler_address_mode(sg_wrap m) { + switch (m) { + case SG_WRAP_REPEAT: + return WGPUAddressMode_Repeat; + case SG_WRAP_CLAMP_TO_EDGE: + case SG_WRAP_CLAMP_TO_BORDER: + return WGPUAddressMode_ClampToEdge; + case SG_WRAP_MIRRORED_REPEAT: + return WGPUAddressMode_MirrorRepeat; + default: + SOKOL_UNREACHABLE; + return WGPUAddressMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUFilterMode _sg_wgpu_sampler_minmag_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return WGPUFilterMode_Nearest; + case SG_FILTER_LINEAR: + return WGPUFilterMode_Linear; + default: + SOKOL_UNREACHABLE; + return WGPUFilterMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUMipmapFilterMode _sg_wgpu_sampler_mipmap_filter(sg_filter f) { + switch (f) { + case SG_FILTER_NEAREST: + return WGPUMipmapFilterMode_Nearest; + case SG_FILTER_LINEAR: + return WGPUMipmapFilterMode_Linear; + default: + SOKOL_UNREACHABLE; + return WGPUMipmapFilterMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_indexformat(sg_index_type t) { + // NOTE: there's no WGPUIndexFormat_None + return (t == SG_INDEXTYPE_UINT16) ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Uint32; +} + +_SOKOL_PRIVATE WGPUIndexFormat _sg_wgpu_stripindexformat(sg_primitive_type prim_type, sg_index_type idx_type) { + if (idx_type == SG_INDEXTYPE_NONE) { + return WGPUIndexFormat_Undefined; + } else if ((prim_type == SG_PRIMITIVETYPE_LINE_STRIP) || (prim_type == SG_PRIMITIVETYPE_TRIANGLE_STRIP)) { + return _sg_wgpu_indexformat(idx_type); + } else { + return WGPUIndexFormat_Undefined; + } +} + +_SOKOL_PRIVATE WGPUVertexStepMode _sg_wgpu_stepmode(sg_vertex_step s) { + return (s == SG_VERTEXSTEP_PER_VERTEX) ? WGPUVertexStepMode_Vertex : WGPUVertexStepMode_Instance; +} + +_SOKOL_PRIVATE WGPUVertexFormat _sg_wgpu_vertexformat(sg_vertex_format f) { + switch (f) { + case SG_VERTEXFORMAT_FLOAT: return WGPUVertexFormat_Float32; + case SG_VERTEXFORMAT_FLOAT2: return WGPUVertexFormat_Float32x2; + case SG_VERTEXFORMAT_FLOAT3: return WGPUVertexFormat_Float32x3; + case SG_VERTEXFORMAT_FLOAT4: return WGPUVertexFormat_Float32x4; + case SG_VERTEXFORMAT_INT: return WGPUVertexFormat_Sint32; + case SG_VERTEXFORMAT_INT2: return WGPUVertexFormat_Sint32x2; + case SG_VERTEXFORMAT_INT3: return WGPUVertexFormat_Sint32x3; + case SG_VERTEXFORMAT_INT4: return WGPUVertexFormat_Sint32x4; + case SG_VERTEXFORMAT_UINT: return WGPUVertexFormat_Uint32; + case SG_VERTEXFORMAT_UINT2: return WGPUVertexFormat_Uint32x2; + case SG_VERTEXFORMAT_UINT3: return WGPUVertexFormat_Uint32x3; + case SG_VERTEXFORMAT_UINT4: return WGPUVertexFormat_Uint32x4; + case SG_VERTEXFORMAT_BYTE4: return WGPUVertexFormat_Sint8x4; + case SG_VERTEXFORMAT_BYTE4N: return WGPUVertexFormat_Snorm8x4; + case SG_VERTEXFORMAT_UBYTE4: return WGPUVertexFormat_Uint8x4; + case SG_VERTEXFORMAT_UBYTE4N: return WGPUVertexFormat_Unorm8x4; + case SG_VERTEXFORMAT_SHORT2: return WGPUVertexFormat_Sint16x2; + case SG_VERTEXFORMAT_SHORT2N: return WGPUVertexFormat_Snorm16x2; + case SG_VERTEXFORMAT_USHORT2: return WGPUVertexFormat_Uint16x2; + case SG_VERTEXFORMAT_USHORT2N: return WGPUVertexFormat_Unorm16x2; + case SG_VERTEXFORMAT_SHORT4: return WGPUVertexFormat_Sint16x4; + case SG_VERTEXFORMAT_SHORT4N: return WGPUVertexFormat_Snorm16x4; + case SG_VERTEXFORMAT_USHORT4: return WGPUVertexFormat_Uint16x4; + case SG_VERTEXFORMAT_USHORT4N: return WGPUVertexFormat_Unorm16x4; + case SG_VERTEXFORMAT_UINT10_N2: return WGPUVertexFormat_Unorm10_10_10_2; + case SG_VERTEXFORMAT_HALF2: return WGPUVertexFormat_Float16x2; + case SG_VERTEXFORMAT_HALF4: return WGPUVertexFormat_Float16x4; + default: + SOKOL_UNREACHABLE; + return WGPUVertexFormat_Force32; + } +} + +_SOKOL_PRIVATE WGPUPrimitiveTopology _sg_wgpu_topology(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return WGPUPrimitiveTopology_PointList; + case SG_PRIMITIVETYPE_LINES: return WGPUPrimitiveTopology_LineList; + case SG_PRIMITIVETYPE_LINE_STRIP: return WGPUPrimitiveTopology_LineStrip; + case SG_PRIMITIVETYPE_TRIANGLES: return WGPUPrimitiveTopology_TriangleList; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return WGPUPrimitiveTopology_TriangleStrip; + default: + SOKOL_UNREACHABLE; + return WGPUPrimitiveTopology_Force32; + } +} + +_SOKOL_PRIVATE WGPUFrontFace _sg_wgpu_frontface(sg_face_winding fw) { + return (fw == SG_FACEWINDING_CCW) ? WGPUFrontFace_CCW : WGPUFrontFace_CW; +} + +_SOKOL_PRIVATE WGPUCullMode _sg_wgpu_cullmode(sg_cull_mode cm) { + switch (cm) { + case SG_CULLMODE_NONE: return WGPUCullMode_None; + case SG_CULLMODE_FRONT: return WGPUCullMode_Front; + case SG_CULLMODE_BACK: return WGPUCullMode_Back; + default: + SOKOL_UNREACHABLE; + return WGPUCullMode_Force32; + } +} + +_SOKOL_PRIVATE WGPUTextureFormat _sg_wgpu_textureformat(sg_pixel_format p) { + switch (p) { + case SG_PIXELFORMAT_NONE: return WGPUTextureFormat_Undefined; + case SG_PIXELFORMAT_R8: return WGPUTextureFormat_R8Unorm; + case SG_PIXELFORMAT_R8SN: return WGPUTextureFormat_R8Snorm; + case SG_PIXELFORMAT_R8UI: return WGPUTextureFormat_R8Uint; + case SG_PIXELFORMAT_R8SI: return WGPUTextureFormat_R8Sint; + case SG_PIXELFORMAT_R16UI: return WGPUTextureFormat_R16Uint; + case SG_PIXELFORMAT_R16SI: return WGPUTextureFormat_R16Sint; + case SG_PIXELFORMAT_R16F: return WGPUTextureFormat_R16Float; + case SG_PIXELFORMAT_RG8: return WGPUTextureFormat_RG8Unorm; + case SG_PIXELFORMAT_RG8SN: return WGPUTextureFormat_RG8Snorm; + case SG_PIXELFORMAT_RG8UI: return WGPUTextureFormat_RG8Uint; + case SG_PIXELFORMAT_RG8SI: return WGPUTextureFormat_RG8Sint; + case SG_PIXELFORMAT_R32UI: return WGPUTextureFormat_R32Uint; + case SG_PIXELFORMAT_R32SI: return WGPUTextureFormat_R32Sint; + case SG_PIXELFORMAT_R32F: return WGPUTextureFormat_R32Float; + case SG_PIXELFORMAT_RG16UI: return WGPUTextureFormat_RG16Uint; + case SG_PIXELFORMAT_RG16SI: return WGPUTextureFormat_RG16Sint; + case SG_PIXELFORMAT_RG16F: return WGPUTextureFormat_RG16Float; + case SG_PIXELFORMAT_RGBA8: return WGPUTextureFormat_RGBA8Unorm; + case SG_PIXELFORMAT_SRGB8A8: return WGPUTextureFormat_RGBA8UnormSrgb; + case SG_PIXELFORMAT_RGBA8SN: return WGPUTextureFormat_RGBA8Snorm; + case SG_PIXELFORMAT_RGBA8UI: return WGPUTextureFormat_RGBA8Uint; + case SG_PIXELFORMAT_RGBA8SI: return WGPUTextureFormat_RGBA8Sint; + case SG_PIXELFORMAT_BGRA8: return WGPUTextureFormat_BGRA8Unorm; + case SG_PIXELFORMAT_RGB10A2: return WGPUTextureFormat_RGB10A2Unorm; + case SG_PIXELFORMAT_RG11B10F: return WGPUTextureFormat_RG11B10Ufloat; + case SG_PIXELFORMAT_RG32UI: return WGPUTextureFormat_RG32Uint; + case SG_PIXELFORMAT_RG32SI: return WGPUTextureFormat_RG32Sint; + case SG_PIXELFORMAT_RG32F: return WGPUTextureFormat_RG32Float; + case SG_PIXELFORMAT_RGBA16UI: return WGPUTextureFormat_RGBA16Uint; + case SG_PIXELFORMAT_RGBA16SI: return WGPUTextureFormat_RGBA16Sint; + case SG_PIXELFORMAT_RGBA16F: return WGPUTextureFormat_RGBA16Float; + case SG_PIXELFORMAT_RGBA32UI: return WGPUTextureFormat_RGBA32Uint; + case SG_PIXELFORMAT_RGBA32SI: return WGPUTextureFormat_RGBA32Sint; + case SG_PIXELFORMAT_RGBA32F: return WGPUTextureFormat_RGBA32Float; + case SG_PIXELFORMAT_DEPTH: return WGPUTextureFormat_Depth32Float; + case SG_PIXELFORMAT_DEPTH_STENCIL: return WGPUTextureFormat_Depth32FloatStencil8; + case SG_PIXELFORMAT_BC1_RGBA: return WGPUTextureFormat_BC1RGBAUnorm; + case SG_PIXELFORMAT_BC2_RGBA: return WGPUTextureFormat_BC2RGBAUnorm; + case SG_PIXELFORMAT_BC3_RGBA: return WGPUTextureFormat_BC3RGBAUnorm; + case SG_PIXELFORMAT_BC3_SRGBA: return WGPUTextureFormat_BC3RGBAUnormSrgb; + case SG_PIXELFORMAT_BC4_R: return WGPUTextureFormat_BC4RUnorm; + case SG_PIXELFORMAT_BC4_RSN: return WGPUTextureFormat_BC4RSnorm; + case SG_PIXELFORMAT_BC5_RG: return WGPUTextureFormat_BC5RGUnorm; + case SG_PIXELFORMAT_BC5_RGSN: return WGPUTextureFormat_BC5RGSnorm; + case SG_PIXELFORMAT_BC6H_RGBF: return WGPUTextureFormat_BC6HRGBFloat; + case SG_PIXELFORMAT_BC6H_RGBUF: return WGPUTextureFormat_BC6HRGBUfloat; + case SG_PIXELFORMAT_BC7_RGBA: return WGPUTextureFormat_BC7RGBAUnorm; + case SG_PIXELFORMAT_BC7_SRGBA: return WGPUTextureFormat_BC7RGBAUnormSrgb; + case SG_PIXELFORMAT_ETC2_RGB8: return WGPUTextureFormat_ETC2RGB8Unorm; + case SG_PIXELFORMAT_ETC2_RGB8A1: return WGPUTextureFormat_ETC2RGB8A1Unorm; + case SG_PIXELFORMAT_ETC2_RGBA8: return WGPUTextureFormat_ETC2RGBA8Unorm; + case SG_PIXELFORMAT_ETC2_SRGB8: return WGPUTextureFormat_ETC2RGB8UnormSrgb; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return WGPUTextureFormat_ETC2RGBA8UnormSrgb; + case SG_PIXELFORMAT_EAC_R11: return WGPUTextureFormat_EACR11Unorm; + case SG_PIXELFORMAT_EAC_R11SN: return WGPUTextureFormat_EACR11Snorm; + case SG_PIXELFORMAT_EAC_RG11: return WGPUTextureFormat_EACRG11Unorm; + case SG_PIXELFORMAT_EAC_RG11SN: return WGPUTextureFormat_EACRG11Snorm; + case SG_PIXELFORMAT_RGB9E5: return WGPUTextureFormat_RGB9E5Ufloat; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return WGPUTextureFormat_ASTC4x4Unorm; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return WGPUTextureFormat_ASTC4x4UnormSrgb; + // NOT SUPPORTED + case SG_PIXELFORMAT_R16: + case SG_PIXELFORMAT_R16SN: + case SG_PIXELFORMAT_RG16: + case SG_PIXELFORMAT_RG16SN: + case SG_PIXELFORMAT_RGBA16: + case SG_PIXELFORMAT_RGBA16SN: + return WGPUTextureFormat_Undefined; + + default: + SOKOL_UNREACHABLE; + return WGPUTextureFormat_Force32; + } +} + +_SOKOL_PRIVATE WGPUCompareFunction _sg_wgpu_comparefunc(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return WGPUCompareFunction_Never; + case SG_COMPAREFUNC_LESS: return WGPUCompareFunction_Less; + case SG_COMPAREFUNC_EQUAL: return WGPUCompareFunction_Equal; + case SG_COMPAREFUNC_LESS_EQUAL: return WGPUCompareFunction_LessEqual; + case SG_COMPAREFUNC_GREATER: return WGPUCompareFunction_Greater; + case SG_COMPAREFUNC_NOT_EQUAL: return WGPUCompareFunction_NotEqual; + case SG_COMPAREFUNC_GREATER_EQUAL: return WGPUCompareFunction_GreaterEqual; + case SG_COMPAREFUNC_ALWAYS: return WGPUCompareFunction_Always; + default: + SOKOL_UNREACHABLE; + return WGPUCompareFunction_Force32; + } +} + +_SOKOL_PRIVATE WGPUStencilOperation _sg_wgpu_stencilop(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return WGPUStencilOperation_Keep; + case SG_STENCILOP_ZERO: return WGPUStencilOperation_Zero; + case SG_STENCILOP_REPLACE: return WGPUStencilOperation_Replace; + case SG_STENCILOP_INCR_CLAMP: return WGPUStencilOperation_IncrementClamp; + case SG_STENCILOP_DECR_CLAMP: return WGPUStencilOperation_DecrementClamp; + case SG_STENCILOP_INVERT: return WGPUStencilOperation_Invert; + case SG_STENCILOP_INCR_WRAP: return WGPUStencilOperation_IncrementWrap; + case SG_STENCILOP_DECR_WRAP: return WGPUStencilOperation_DecrementWrap; + default: + SOKOL_UNREACHABLE; + return WGPUStencilOperation_Force32; + } +} + +_SOKOL_PRIVATE WGPUBlendOperation _sg_wgpu_blendop(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return WGPUBlendOperation_Add; + case SG_BLENDOP_SUBTRACT: return WGPUBlendOperation_Subtract; + case SG_BLENDOP_REVERSE_SUBTRACT: return WGPUBlendOperation_ReverseSubtract; + case SG_BLENDOP_MIN: return WGPUBlendOperation_Min; + case SG_BLENDOP_MAX: return WGPUBlendOperation_Max; + default: + SOKOL_UNREACHABLE; + return WGPUBlendOperation_Force32; + } +} + +_SOKOL_PRIVATE WGPUBlendFactor _sg_wgpu_blendfactor(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return WGPUBlendFactor_Zero; + case SG_BLENDFACTOR_ONE: return WGPUBlendFactor_One; + case SG_BLENDFACTOR_SRC_COLOR: return WGPUBlendFactor_Src; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return WGPUBlendFactor_OneMinusSrc; + case SG_BLENDFACTOR_SRC_ALPHA: return WGPUBlendFactor_SrcAlpha; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return WGPUBlendFactor_OneMinusSrcAlpha; + case SG_BLENDFACTOR_DST_COLOR: return WGPUBlendFactor_Dst; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return WGPUBlendFactor_OneMinusDst; + case SG_BLENDFACTOR_DST_ALPHA: return WGPUBlendFactor_DstAlpha; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return WGPUBlendFactor_OneMinusDstAlpha; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return WGPUBlendFactor_SrcAlphaSaturated; + case SG_BLENDFACTOR_BLEND_COLOR: return WGPUBlendFactor_Constant; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return WGPUBlendFactor_OneMinusConstant; + // FIXME: separate blend alpha value not supported? + case SG_BLENDFACTOR_BLEND_ALPHA: return WGPUBlendFactor_Constant; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return WGPUBlendFactor_OneMinusConstant; + default: + SOKOL_UNREACHABLE; + return WGPUBlendFactor_Force32; + } +} + +_SOKOL_PRIVATE WGPUColorWriteMask _sg_wgpu_colorwritemask(uint8_t m) { + // FIXME: change to WGPUColorWriteMask once Emscripten and Dawn webgpu.h agree + int res = 0; + if (0 != (m & SG_COLORMASK_R)) { + res |= WGPUColorWriteMask_Red; + } + if (0 != (m & SG_COLORMASK_G)) { + res |= WGPUColorWriteMask_Green; + } + if (0 != (m & SG_COLORMASK_B)) { + res |= WGPUColorWriteMask_Blue; + } + if (0 != (m & SG_COLORMASK_A)) { + res |= WGPUColorWriteMask_Alpha; + } + return (WGPUColorWriteMask)res; +} + +_SOKOL_PRIVATE WGPUShaderStage _sg_wgpu_shader_stage(sg_shader_stage stage) { + switch (stage) { + case SG_SHADERSTAGE_VERTEX: return WGPUShaderStage_Vertex; + case SG_SHADERSTAGE_FRAGMENT: return WGPUShaderStage_Fragment; + case SG_SHADERSTAGE_COMPUTE: return WGPUShaderStage_Compute; + default: SOKOL_UNREACHABLE; return WGPUShaderStage_None; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_init_caps(void) { + _sg.backend = SG_BACKEND_WGPU; + _sg.features.origin_top_left = true; + _sg.features.image_clamp_to_border = false; + _sg.features.mrt_independent_blend_state = true; + _sg.features.mrt_independent_write_mask = true; + _sg.features.compute = true; + _sg.features.msaa_image_bindings = true; + + wgpuDeviceGetLimits(_sg.wgpu.dev, &_sg.wgpu.limits); + + const WGPULimits* l = &_sg.wgpu.limits.limits; + _sg.limits.max_image_size_2d = (int) l->maxTextureDimension2D; + _sg.limits.max_image_size_cube = (int) l->maxTextureDimension2D; // not a bug, see: https://github.com/gpuweb/gpuweb/issues/1327 + _sg.limits.max_image_size_3d = (int) l->maxTextureDimension3D; + _sg.limits.max_image_size_array = (int) l->maxTextureDimension2D; + _sg.limits.max_image_array_layers = (int) l->maxTextureArrayLayers; + _sg.limits.max_vertex_attrs = SG_MAX_VERTEX_ATTRIBUTES; + + // NOTE: no WGPUTextureFormat_R16Unorm + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_SRGB8A8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_BGRA8]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_R16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RG16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGBA16F]); + _sg_pixelformat_all(&_sg.formats[SG_PIXELFORMAT_RGB10A2]); + + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_R8SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG8SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGBA8SN]); + + // FIXME: can be made renderable via extension + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RG11B10F]); + + // NOTE: msaa rendering is possible in WebGPU, but no resolve + // which is a combination that's not currently supported in sokol-gfx + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R8UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG8UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA8UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA8SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R16UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R16SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG16UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG16SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA16UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA16SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32SI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32UI]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32SI]); + + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_Float32Filterable)) { + _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sfr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } else { + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_R32F]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RG32F]); + _sg_pixelformat_sr(&_sg.formats[SG_PIXELFORMAT_RGBA32F]); + } + + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH]); + _sg_pixelformat_srmd(&_sg.formats[SG_PIXELFORMAT_DEPTH_STENCIL]); + + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_RGB9E5]); + + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionBC)) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC1_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC2_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC3_SRGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_R]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC4_RSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RG]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC5_RGSN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC6H_RGBUF]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_BC7_SRGBA]); + } + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionETC2)) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGB8A1]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_RGBA8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_SRGB8A8]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_R11SN]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_EAC_RG11SN]); + } + + if (wgpuDeviceHasFeature(_sg.wgpu.dev, WGPUFeatureName_TextureCompressionASTC)) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_RGBA]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ASTC_4x4_SRGBA]); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_uniform_buffer_init(const sg_desc* desc) { + SOKOL_ASSERT(0 == _sg.wgpu.uniform.staging); + SOKOL_ASSERT(0 == _sg.wgpu.uniform.buf); + + // Add the max-uniform-update size (64 KB) to the requested buffer size, + // this is to prevent validation errors in the WebGPU implementation + // if the entire buffer size is used per frame. 64 KB is the allowed + // max uniform update size on NVIDIA + // + // FIXME: is this still needed? + _sg.wgpu.uniform.num_bytes = (uint32_t)(desc->uniform_buffer_size + _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE); + _sg.wgpu.uniform.staging = (uint8_t*)_sg_malloc(_sg.wgpu.uniform.num_bytes); + + WGPUBufferDescriptor ub_desc; + _sg_clear(&ub_desc, sizeof(ub_desc)); + ub_desc.size = _sg.wgpu.uniform.num_bytes; + ub_desc.usage = WGPUBufferUsage_Uniform|WGPUBufferUsage_CopyDst; + _sg.wgpu.uniform.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &ub_desc); + SOKOL_ASSERT(_sg.wgpu.uniform.buf); +} + +_SOKOL_PRIVATE void _sg_wgpu_uniform_buffer_discard(void) { + if (_sg.wgpu.uniform.buf) { + wgpuBufferRelease(_sg.wgpu.uniform.buf); + _sg.wgpu.uniform.buf = 0; + } + if (_sg.wgpu.uniform.staging) { + _sg_free(_sg.wgpu.uniform.staging); + _sg.wgpu.uniform.staging = 0; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_uniform_buffer_on_commit(void) { + wgpuQueueWriteBuffer(_sg.wgpu.queue, _sg.wgpu.uniform.buf, 0, _sg.wgpu.uniform.staging, _sg.wgpu.uniform.offset); + _sg_stats_add(wgpu.uniforms.size_write_buffer, _sg.wgpu.uniform.offset); + _sg.wgpu.uniform.offset = 0; + _sg_clear(_sg.wgpu.uniform.bind_offsets, sizeof(_sg.wgpu.uniform.bind_offsets)); +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_pool_init(const sg_desc* desc) { + SOKOL_ASSERT((desc->wgpu_bindgroups_cache_size > 0) && (desc->wgpu_bindgroups_cache_size < _SG_MAX_POOL_SIZE)); + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + SOKOL_ASSERT(0 == p->bindgroups); + const int pool_size = desc->wgpu_bindgroups_cache_size; + _sg_pool_init(&p->pool, pool_size); + size_t pool_byte_size = sizeof(_sg_wgpu_bindgroup_t) * (size_t)p->pool.size; + p->bindgroups = (_sg_wgpu_bindgroup_t*) _sg_malloc_clear(pool_byte_size); +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_pool_discard(void) { + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + SOKOL_ASSERT(p->bindgroups); + _sg_free(p->bindgroups); p->bindgroups = 0; + _sg_pool_discard(&p->pool); +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_bindgroup_at(uint32_t bg_id) { + SOKOL_ASSERT(SG_INVALID_ID != bg_id); + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + int slot_index = _sg_slot_index(bg_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->bindgroups[slot_index]; +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_lookup_bindgroup(uint32_t bg_id) { + if (SG_INVALID_ID != bg_id) { + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_bindgroup_at(bg_id); + if (bg->slot.id == bg_id) { + return bg; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_handle_t _sg_wgpu_alloc_bindgroup(void) { + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + _sg_wgpu_bindgroup_handle_t res; + int slot_index = _sg_pool_alloc_index(&p->pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&p->pool, &p->bindgroups[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(WGPU_BINDGROUPS_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE void _sg_wgpu_dealloc_bindgroup(_sg_wgpu_bindgroup_t* bg) { + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_ALLOC) && (bg->slot.id != SG_INVALID_ID)); + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + _sg_pool_free_index(&p->pool, _sg_slot_index(bg->slot.id)); + _sg_slot_reset(&bg->slot); +} + +_SOKOL_PRIVATE void _sg_wgpu_reset_bindgroup_to_alloc_state(_sg_wgpu_bindgroup_t* bg) { + SOKOL_ASSERT(bg); + _sg_slot_t slot = bg->slot; + _sg_clear(bg, sizeof(_sg_wgpu_bindgroup_t)); + bg->slot = slot; + bg->slot.state = SG_RESOURCESTATE_ALLOC; +} + +// MurmurHash64B (see: https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash2.cpp#L142) +_SOKOL_PRIVATE uint64_t _sg_wgpu_hash(const void* key, int len, uint64_t seed) { + const uint32_t m = 0x5bd1e995; + const int r = 24; + uint32_t h1 = (uint32_t)seed ^ (uint32_t)len; + uint32_t h2 = (uint32_t)(seed >> 32); + const uint32_t * data = (const uint32_t *)key; + while (len >= 8) { + uint32_t k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + len -= 4; + uint32_t k2 = *data++; + k2 *= m; k2 ^= k2 >> r; k2 *= m; + h2 *= m; h2 ^= k2; + len -= 4; + } + if (len >= 4) { + uint32_t k1 = *data++; + k1 *= m; k1 ^= k1 >> r; k1 *= m; + h1 *= m; h1 ^= k1; + len -= 4; + } + switch(len) { + case 3: h2 ^= (uint32_t)(((unsigned char*)data)[2] << 16); + case 2: h2 ^= (uint32_t)(((unsigned char*)data)[1] << 8); + case 1: h2 ^= ((unsigned char*)data)[0]; + h2 *= m; + }; + h1 ^= h2 >> 18; h1 *= m; + h2 ^= h1 >> 22; h2 *= m; + h1 ^= h2 >> 17; h1 *= m; + h2 ^= h1 >> 19; h2 *= m; + uint64_t h = h1; + h = (h << 32) | h2; + return h; +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_item(_sg_wgpu_bindgroups_cache_item_type_t type, uint8_t wgpu_binding, uint32_t id) { + // key pattern is bbbbttttiiiiiiii + const uint64_t bb = (uint64_t)wgpu_binding; + const uint64_t tttt = (uint64_t)type; + const uint64_t iiiiiiii = (uint64_t)id; + return (bb << 56) | (bb << 48) | (tttt << 32) | iiiiiiii; +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_pip_item(uint32_t id) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE, 0xFF, id); +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_image_item(uint8_t wgpu_binding, uint32_t id) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_IMAGE, wgpu_binding, id); +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_sampler_item(uint8_t wgpu_binding, uint32_t id) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER, wgpu_binding, id); +} + +_SOKOL_PRIVATE uint64_t _sg_wgpu_bindgroups_cache_sbuf_item(uint8_t wgpu_binding, uint32_t id) { + return _sg_wgpu_bindgroups_cache_item(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_STORAGEBUFFER, wgpu_binding, id); +} + +_SOKOL_PRIVATE void _sg_wgpu_init_bindgroups_cache_key(_sg_wgpu_bindgroups_cache_key_t* key, const _sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + const _sg_shader_t* shd = bnd->pip->shader; + SOKOL_ASSERT(shd && shd->slot.id == bnd->pip->cmn.shader_id.id); + + _sg_clear(key->items, sizeof(key->items)); + key->items[0] = _sg_wgpu_bindgroups_cache_pip_item(bnd->pip->slot.id); + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->imgs[i]); + const size_t item_idx = i + 1; + SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS); + SOKOL_ASSERT(0 == key->items[item_idx]); + const uint8_t wgpu_binding = shd->wgpu.img_grp1_bnd_n[i]; + const uint32_t id = bnd->imgs[i]->slot.id; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_image_item(wgpu_binding, id); + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->smps[i]); + const size_t item_idx = i + 1 + SG_MAX_IMAGE_BINDSLOTS; + SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS); + SOKOL_ASSERT(0 == key->items[item_idx]); + const uint8_t wgpu_binding = shd->wgpu.smp_grp1_bnd_n[i]; + const uint32_t id = bnd->smps[i]->slot.id; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_sampler_item(wgpu_binding, id); + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->sbufs[i]); + const size_t item_idx = i + 1 + SG_MAX_IMAGE_BINDSLOTS + SG_MAX_SAMPLER_BINDSLOTS; + SOKOL_ASSERT(item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS); + SOKOL_ASSERT(0 == key->items[item_idx]); + const uint8_t wgpu_binding = shd->wgpu.sbuf_grp1_bnd_n[i]; + const uint32_t id = bnd->sbufs[i]->slot.id; + key->items[item_idx] = _sg_wgpu_bindgroups_cache_sbuf_item(wgpu_binding, id); + } + key->hash = _sg_wgpu_hash(&key->items, (int)sizeof(key->items), 0x1234567887654321); +} + +_SOKOL_PRIVATE bool _sg_wgpu_compare_bindgroups_cache_key(_sg_wgpu_bindgroups_cache_key_t* k0, _sg_wgpu_bindgroups_cache_key_t* k1) { + SOKOL_ASSERT(k0 && k1); + if (k0->hash != k1->hash) { + return false; + } + if (memcmp(&k0->items, &k1->items, sizeof(k0->items)) != 0) { + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_hash_vs_key_mismatch, 1); + return false; + } + return true; +} + +_SOKOL_PRIVATE _sg_wgpu_bindgroup_t* _sg_wgpu_create_bindgroup(_sg_bindings_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.dev); + SOKOL_ASSERT(bnd->pip); + const _sg_shader_t* shd = bnd->pip->shader; + SOKOL_ASSERT(shd && (shd->slot.id == bnd->pip->cmn.shader_id.id)); + _sg_stats_add(wgpu.bindings.num_create_bindgroup, 1); + _sg_wgpu_bindgroup_handle_t bg_id = _sg_wgpu_alloc_bindgroup(); + if (bg_id.id == SG_INVALID_ID) { + return 0; + } + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_bindgroup_at(bg_id.id); + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_ALLOC)); + + // create wgpu bindgroup object (also see _sg_wgpu_create_shader()) + WGPUBindGroupLayout bgl = bnd->pip->shader->wgpu.bgl_img_smp_sbuf; + SOKOL_ASSERT(bgl); + WGPUBindGroupEntry bg_entries[_SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES]; + _sg_clear(&bg_entries, sizeof(bg_entries)); + size_t bgl_index = 0; + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->imgs[i]); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.img_grp1_bnd_n[i]; + bg_entry->textureView = bnd->imgs[i]->wgpu.view; + bgl_index += 1; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->smps[i]); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.smp_grp1_bnd_n[i]; + bg_entry->sampler = bnd->smps[i]->wgpu.smp; + bgl_index += 1; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + SOKOL_ASSERT(bnd->sbufs[i]); + SOKOL_ASSERT(bgl_index < _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + bg_entry->binding = shd->wgpu.sbuf_grp1_bnd_n[i]; + bg_entry->buffer = bnd->sbufs[i]->wgpu.buf; + bg_entry->size = (uint64_t) bnd->sbufs[i]->cmn.size; + bgl_index += 1; + } + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = bgl; + bg_desc.entryCount = bgl_index; + bg_desc.entries = bg_entries; + bg->bindgroup = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + if (bg->bindgroup == 0) { + _SG_ERROR(WGPU_CREATEBINDGROUP_FAILED); + bg->slot.state = SG_RESOURCESTATE_FAILED; + return bg; + } + _sg_wgpu_init_bindgroups_cache_key(&bg->key, bnd); + bg->slot.state = SG_RESOURCESTATE_VALID; + return bg; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_bindgroup(_sg_wgpu_bindgroup_t* bg) { + SOKOL_ASSERT(bg); + _sg_stats_add(wgpu.bindings.num_discard_bindgroup, 1); + if (bg->slot.state == SG_RESOURCESTATE_VALID) { + if (bg->bindgroup) { + wgpuBindGroupRelease(bg->bindgroup); + bg->bindgroup = 0; + } + _sg_wgpu_reset_bindgroup_to_alloc_state(bg); + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (bg->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_wgpu_dealloc_bindgroup(bg); + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_INITIAL); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_all_bindgroups(void) { + _sg_wgpu_bindgroups_pool_t* p = &_sg.wgpu.bindgroups_pool; + for (int i = 0; i < p->pool.size; i++) { + sg_resource_state state = p->bindgroups[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_wgpu_discard_bindgroup(&p->bindgroups[i]); + } + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_init(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.num == 0); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.index_mask == 0); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items == 0); + const int num = desc->wgpu_bindgroups_cache_size; + if (num <= 1) { + _SG_PANIC(WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE); + } + if (!_sg_ispow2(num)) { + _SG_PANIC(WGPU_BINDGROUPSCACHE_SIZE_POW2); + } + _sg.wgpu.bindgroups_cache.num = (uint32_t)desc->wgpu_bindgroups_cache_size; + _sg.wgpu.bindgroups_cache.index_mask = _sg.wgpu.bindgroups_cache.num - 1; + size_t size_in_bytes = sizeof(_sg_wgpu_bindgroup_handle_t) * (size_t)num; + _sg.wgpu.bindgroups_cache.items = (_sg_wgpu_bindgroup_handle_t*)_sg_malloc_clear(size_in_bytes); +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_discard(void) { + if (_sg.wgpu.bindgroups_cache.items) { + _sg_free(_sg.wgpu.bindgroups_cache.items); + _sg.wgpu.bindgroups_cache.items = 0; + } + _sg.wgpu.bindgroups_cache.num = 0; + _sg.wgpu.bindgroups_cache.index_mask = 0; +} + +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_set(uint64_t hash, uint32_t bg_id) { + uint32_t index = hash & _sg.wgpu.bindgroups_cache.index_mask; + SOKOL_ASSERT(index < _sg.wgpu.bindgroups_cache.num); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items); + _sg.wgpu.bindgroups_cache.items[index].id = bg_id; +} + +_SOKOL_PRIVATE uint32_t _sg_wgpu_bindgroups_cache_get(uint64_t hash) { + uint32_t index = hash & _sg.wgpu.bindgroups_cache.index_mask; + SOKOL_ASSERT(index < _sg.wgpu.bindgroups_cache.num); + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items); + return _sg.wgpu.bindgroups_cache.items[index].id; +} + +// called from wgpu resource destroy functions to also invalidate any +// bindgroups cache slot and bindgroup referencing that resource +_SOKOL_PRIVATE void _sg_wgpu_bindgroups_cache_invalidate(_sg_wgpu_bindgroups_cache_item_type_t type, uint32_t id) { + const uint64_t key_mask = 0x0000FFFFFFFFFFFF; + const uint64_t key_item = _sg_wgpu_bindgroups_cache_item(type, 0, id) & key_mask; + SOKOL_ASSERT(_sg.wgpu.bindgroups_cache.items); + for (uint32_t cache_item_idx = 0; cache_item_idx < _sg.wgpu.bindgroups_cache.num; cache_item_idx++) { + const uint32_t bg_id = _sg.wgpu.bindgroups_cache.items[cache_item_idx].id; + if (bg_id != SG_INVALID_ID) { + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_lookup_bindgroup(bg_id); + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_VALID)); + // check if resource is in bindgroup, if yes discard bindgroup and invalidate cache slot + bool invalidate_cache_item = false; + for (int key_item_idx = 0; key_item_idx < _SG_WGPU_BINDGROUPSCACHEKEY_NUM_ITEMS; key_item_idx++) { + if ((bg->key.items[key_item_idx] & key_mask) == key_item) { + invalidate_cache_item = true; + break; + } + } + if (invalidate_cache_item) { + _sg_wgpu_discard_bindgroup(bg); bg = 0; + _sg_wgpu_bindgroups_cache_set(cache_item_idx, SG_INVALID_ID); + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_invalidates, 1); + } + } + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_clear(void) { + memset(&_sg.wgpu.bindings_cache, 0, sizeof(_sg.wgpu.bindings_cache)); +} + +_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_vb_dirty(size_t index, const _sg_buffer_t* vb, uint64_t offset) { + SOKOL_ASSERT((index >= 0) && (index < SG_MAX_VERTEXBUFFER_BINDSLOTS)); + if (vb) { + return (_sg.wgpu.bindings_cache.vbs[index].buffer.id != vb->slot.id) + || (_sg.wgpu.bindings_cache.vbs[index].offset != offset); + } else { + return _sg.wgpu.bindings_cache.vbs[index].buffer.id != SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_vb_update(size_t index, const _sg_buffer_t* vb, uint64_t offset) { + SOKOL_ASSERT((index >= 0) && (index < SG_MAX_VERTEXBUFFER_BINDSLOTS)); + if (vb) { + _sg.wgpu.bindings_cache.vbs[index].buffer.id = vb->slot.id; + _sg.wgpu.bindings_cache.vbs[index].offset = offset; + } else { + _sg.wgpu.bindings_cache.vbs[index].buffer.id = SG_INVALID_ID; + _sg.wgpu.bindings_cache.vbs[index].offset = 0; + } +} + +_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_ib_dirty(const _sg_buffer_t* ib, uint64_t offset) { + if (ib) { + return (_sg.wgpu.bindings_cache.ib.buffer.id != ib->slot.id) + || (_sg.wgpu.bindings_cache.ib.offset != offset); + } else { + return _sg.wgpu.bindings_cache.ib.buffer.id != SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_ib_update(const _sg_buffer_t* ib, uint64_t offset) { + if (ib) { + _sg.wgpu.bindings_cache.ib.buffer.id = ib->slot.id; + _sg.wgpu.bindings_cache.ib.offset = offset; + } else { + _sg.wgpu.bindings_cache.ib.buffer.id = SG_INVALID_ID; + _sg.wgpu.bindings_cache.ib.offset = 0; + } +} + +_SOKOL_PRIVATE bool _sg_wgpu_bindings_cache_bg_dirty(const _sg_wgpu_bindgroup_t* bg) { + if (bg) { + return _sg.wgpu.bindings_cache.bg.id != bg->slot.id; + } else { + return _sg.wgpu.bindings_cache.bg.id != SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_bindings_cache_bg_update(const _sg_wgpu_bindgroup_t* bg) { + if (bg) { + _sg.wgpu.bindings_cache.bg.id = bg->slot.id; + } else { + _sg.wgpu.bindings_cache.bg.id = SG_INVALID_ID; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_set_img_smp_sbuf_bindgroup(_sg_wgpu_bindgroup_t* bg) { + if (_sg_wgpu_bindings_cache_bg_dirty(bg)) { + _sg_wgpu_bindings_cache_bg_update(bg); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); + if (_sg.cur_pass.is_compute) { + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + if (bg) { + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(bg->bindgroup); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX, bg->bindgroup, 0, 0); + } else { + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + } + } else { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + if (bg) { + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(bg->bindgroup); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX, bg->bindgroup, 0, 0); + } else { + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + } + } + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_bindgroup, 1); + } +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_bindgroup(_sg_bindings_t* bnd) { + if (!_sg.desc.wgpu_disable_bindgroups_cache) { + _sg_wgpu_bindgroup_t* bg = 0; + _sg_wgpu_bindgroups_cache_key_t key; + _sg_wgpu_init_bindgroups_cache_key(&key, bnd); + uint32_t bg_id = _sg_wgpu_bindgroups_cache_get(key.hash); + if (bg_id != SG_INVALID_ID) { + // potential cache hit + bg = _sg_wgpu_lookup_bindgroup(bg_id); + SOKOL_ASSERT(bg && (bg->slot.state == SG_RESOURCESTATE_VALID)); + if (!_sg_wgpu_compare_bindgroups_cache_key(&key, &bg->key)) { + // cache collision, need to delete cached bindgroup + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_collisions, 1); + _sg_wgpu_discard_bindgroup(bg); + _sg_wgpu_bindgroups_cache_set(key.hash, SG_INVALID_ID); + bg = 0; + } else { + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_hits, 1); + } + } else { + _sg_stats_add(wgpu.bindings.num_bindgroup_cache_misses, 1); + } + if (bg == 0) { + // either no cache entry yet, or cache collision, create new bindgroup and store in cache + bg = _sg_wgpu_create_bindgroup(bnd); + _sg_wgpu_bindgroups_cache_set(key.hash, bg->slot.id); + } + if (bg && bg->slot.state == SG_RESOURCESTATE_VALID) { + _sg_wgpu_set_img_smp_sbuf_bindgroup(bg); + } else { + return false; + } + } else { + // bindgroups cache disabled, create and destroy bindgroup on the fly (expensive!) + _sg_wgpu_bindgroup_t* bg = _sg_wgpu_create_bindgroup(bnd); + if (bg) { + if (bg->slot.state == SG_RESOURCESTATE_VALID) { + _sg_wgpu_set_img_smp_sbuf_bindgroup(bg); + } + _sg_wgpu_discard_bindgroup(bg); + } else { + return false; + } + } + return true; +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_index_buffer(_sg_bindings_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + const _sg_buffer_t* ib = bnd->ib; + uint64_t offset = (uint64_t)bnd->ib_offset; + if (_sg_wgpu_bindings_cache_ib_dirty(ib, offset)) { + _sg_wgpu_bindings_cache_ib_update(ib, offset); + if (ib) { + const WGPUIndexFormat format = _sg_wgpu_indexformat(bnd->pip->cmn.index_type); + const uint64_t buf_size = (uint64_t)ib->cmn.size; + SOKOL_ASSERT(buf_size > offset); + const uint64_t max_bytes = buf_size - offset; + wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.rpass_enc, ib->wgpu.buf, format, offset, max_bytes); + /* FIXME: the else-pass should actually set a null index buffer, but that doesn't seem to work yet + } else { + wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.rpass_enc, 0, WGPUIndexFormat_Undefined, 0, 0); + */ + } + _sg_stats_add(wgpu.bindings.num_set_index_buffer, 1); + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_index_buffer, 1); + } + return true; +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_vertex_buffers(_sg_bindings_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + for (uint32_t slot = 0; slot < SG_MAX_VERTEXBUFFER_BINDSLOTS; slot++) { + const _sg_buffer_t* vb = bnd->vbs[slot]; + const uint64_t offset = (uint64_t)bnd->vb_offsets[slot]; + if (_sg_wgpu_bindings_cache_vb_dirty(slot, vb, offset)) { + _sg_wgpu_bindings_cache_vb_update(slot, vb, offset); + if (vb) { + const uint64_t buf_size = (uint64_t)vb->cmn.size; + SOKOL_ASSERT(buf_size > offset); + const uint64_t max_bytes = buf_size - offset; + wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.rpass_enc, slot, vb->wgpu.buf, offset, max_bytes); + /* FIXME: the else-pass should actually set a null vertex buffer, but that doesn't seem to work yet + } else { + wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.rpass_enc, slot, 0, 0, 0); + */ + } + _sg_stats_add(wgpu.bindings.num_set_vertex_buffer, 1); + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_vertex_buffer, 1); + } + } + return true; +} + +_SOKOL_PRIVATE void _sg_wgpu_setup_backend(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->environment.wgpu.device); + SOKOL_ASSERT(desc->uniform_buffer_size > 0); + _sg.backend = SG_BACKEND_WGPU; + _sg.wgpu.valid = true; + _sg.wgpu.dev = (WGPUDevice) desc->environment.wgpu.device; + _sg.wgpu.queue = wgpuDeviceGetQueue(_sg.wgpu.dev); + SOKOL_ASSERT(_sg.wgpu.queue); + + _sg_wgpu_init_caps(); + _sg_wgpu_uniform_buffer_init(desc); + _sg_wgpu_bindgroups_pool_init(desc); + _sg_wgpu_bindgroups_cache_init(desc); + _sg_wgpu_bindings_cache_clear(); + + // create an empty bind group + WGPUBindGroupLayoutDescriptor bgl_desc; + _sg_clear(&bgl_desc, sizeof(bgl_desc)); + WGPUBindGroupLayout empty_bgl = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + SOKOL_ASSERT(empty_bgl); + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = empty_bgl; + _sg.wgpu.empty_bind_group = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + SOKOL_ASSERT(_sg.wgpu.empty_bind_group); + wgpuBindGroupLayoutRelease(empty_bgl); + + // create initial per-frame command encoder + WGPUCommandEncoderDescriptor cmd_enc_desc; + _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); + _sg.wgpu.cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_backend(void) { + SOKOL_ASSERT(_sg.wgpu.valid); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + _sg.wgpu.valid = false; + _sg_wgpu_discard_all_bindgroups(); + _sg_wgpu_bindgroups_cache_discard(); + _sg_wgpu_bindgroups_pool_discard(); + _sg_wgpu_uniform_buffer_discard(); + wgpuBindGroupRelease(_sg.wgpu.empty_bind_group); _sg.wgpu.empty_bind_group = 0; + wgpuCommandEncoderRelease(_sg.wgpu.cmd_enc); _sg.wgpu.cmd_enc = 0; + wgpuQueueRelease(_sg.wgpu.queue); _sg.wgpu.queue = 0; +} + +_SOKOL_PRIVATE void _sg_wgpu_reset_state_cache(void) { + _sg_wgpu_bindings_cache_clear(); +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + SOKOL_ASSERT(buf->cmn.size > 0); + const bool injected = (0 != desc->wgpu_buffer); + if (injected) { + buf->wgpu.buf = (WGPUBuffer) desc->wgpu_buffer; + wgpuBufferReference(buf->wgpu.buf); + } else { + // buffer mapping size must be multiple of 4, so round up buffer size (only a problem + // with index buffers containing odd number of indices) + const uint64_t wgpu_buf_size = _sg_roundup_u64((uint64_t)buf->cmn.size, 4); + const bool map_at_creation = (SG_USAGE_IMMUTABLE == buf->cmn.usage) && (desc->data.ptr); + + WGPUBufferDescriptor wgpu_buf_desc; + _sg_clear(&wgpu_buf_desc, sizeof(wgpu_buf_desc)); + wgpu_buf_desc.usage = _sg_wgpu_buffer_usage(buf->cmn.type, buf->cmn.usage); + wgpu_buf_desc.size = wgpu_buf_size; + wgpu_buf_desc.mappedAtCreation = map_at_creation; + wgpu_buf_desc.label = _sg_wgpu_stringview(desc->label); + buf->wgpu.buf = wgpuDeviceCreateBuffer(_sg.wgpu.dev, &wgpu_buf_desc); + if (0 == buf->wgpu.buf) { + _SG_ERROR(WGPU_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + // NOTE: assume that WebGPU creates zero-initialized buffers + if (map_at_creation) { + SOKOL_ASSERT(desc->data.ptr && (desc->data.size > 0)); + SOKOL_ASSERT(desc->data.size <= (size_t)buf->cmn.size); + // FIXME: inefficient on WASM + void* ptr = wgpuBufferGetMappedRange(buf->wgpu.buf, 0, wgpu_buf_size); + SOKOL_ASSERT(ptr); + memcpy(ptr, desc->data.ptr, desc->data.size); + wgpuBufferUnmap(buf->wgpu.buf); + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + if (buf->cmn.type == SG_BUFFERTYPE_STORAGEBUFFER) { + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_STORAGEBUFFER, buf->slot.id); + } + if (buf->wgpu.buf) { + wgpuBufferRelease(buf->wgpu.buf); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_copy_buffer_data(const _sg_buffer_t* buf, uint64_t offset, const sg_range* data) { + SOKOL_ASSERT((offset + data->size) <= (size_t)buf->cmn.size); + // WebGPU's write-buffer requires the size to be a multiple of four, so we may need to split the copy + // operation into two writeBuffer calls + uint64_t clamped_size = data->size & ~3UL; + uint64_t extra_size = data->size & 3UL; + SOKOL_ASSERT(extra_size < 4); + wgpuQueueWriteBuffer(_sg.wgpu.queue, buf->wgpu.buf, offset, data->ptr, clamped_size); + if (extra_size > 0) { + const uint64_t extra_src_offset = clamped_size; + const uint64_t extra_dst_offset = offset + clamped_size; + uint8_t extra_data[4] = { 0 }; + uint8_t* extra_src_ptr = ((uint8_t*)data->ptr) + extra_src_offset; + for (size_t i = 0; i < extra_size; i++) { + extra_data[i] = extra_src_ptr[i]; + } + wgpuQueueWriteBuffer(_sg.wgpu.queue, buf->wgpu.buf, extra_dst_offset, extra_src_ptr, 4); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_copy_image_data(const _sg_image_t* img, WGPUTexture wgpu_tex, const sg_image_data* data) { + WGPUTextureDataLayout wgpu_layout; + _sg_clear(&wgpu_layout, sizeof(wgpu_layout)); + WGPUImageCopyTexture wgpu_copy_tex; + _sg_clear(&wgpu_copy_tex, sizeof(wgpu_copy_tex)); + wgpu_copy_tex.texture = wgpu_tex; + wgpu_copy_tex.aspect = WGPUTextureAspect_All; + WGPUExtent3D wgpu_extent; + _sg_clear(&wgpu_extent, sizeof(wgpu_extent)); + const int num_faces = (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1; + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < img->cmn.num_mipmaps; mip_index++) { + wgpu_copy_tex.mipLevel = (uint32_t)mip_index; + wgpu_copy_tex.origin.z = (uint32_t)face_index; + int mip_width = _sg_miplevel_dim(img->cmn.width, mip_index); + int mip_height = _sg_miplevel_dim(img->cmn.height, mip_index); + int mip_slices; + switch (img->cmn.type) { + case SG_IMAGETYPE_CUBE: + mip_slices = 1; + break; + case SG_IMAGETYPE_3D: + mip_slices = _sg_miplevel_dim(img->cmn.num_slices, mip_index); + break; + default: + mip_slices = img->cmn.num_slices; + break; + } + const int row_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + const int num_rows = _sg_num_rows(img->cmn.pixel_format, mip_height); + if (_sg_is_compressed_pixel_format(img->cmn.pixel_format)) { + mip_width = _sg_roundup(mip_width, 4); + mip_height = _sg_roundup(mip_height, 4); + } + wgpu_layout.offset = 0; + wgpu_layout.bytesPerRow = (uint32_t)row_pitch; + wgpu_layout.rowsPerImage = (uint32_t)num_rows; + wgpu_extent.width = (uint32_t)mip_width; + wgpu_extent.height = (uint32_t)mip_height; + wgpu_extent.depthOrArrayLayers = (uint32_t)mip_slices; + const sg_range* mip_data = &data->subimage[face_index][mip_index]; + wgpuQueueWriteTexture(_sg.wgpu.queue, &wgpu_copy_tex, mip_data->ptr, mip_data->size, &wgpu_layout, &wgpu_extent); + } + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && desc); + const bool injected = (0 != desc->wgpu_texture); + if (injected) { + img->wgpu.tex = (WGPUTexture)desc->wgpu_texture; + wgpuTextureReference(img->wgpu.tex); + img->wgpu.view = (WGPUTextureView)desc->wgpu_texture_view; + if (img->wgpu.view) { + wgpuTextureViewReference(img->wgpu.view); + } + } else { + WGPUTextureDescriptor wgpu_tex_desc; + _sg_clear(&wgpu_tex_desc, sizeof(wgpu_tex_desc)); + wgpu_tex_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_tex_desc.usage = WGPUTextureUsage_TextureBinding|WGPUTextureUsage_CopyDst; + if (desc->render_target) { + wgpu_tex_desc.usage |= WGPUTextureUsage_RenderAttachment; + } + wgpu_tex_desc.dimension = _sg_wgpu_texture_dimension(img->cmn.type); + wgpu_tex_desc.size.width = (uint32_t) img->cmn.width; + wgpu_tex_desc.size.height = (uint32_t) img->cmn.height; + if (desc->type == SG_IMAGETYPE_CUBE) { + wgpu_tex_desc.size.depthOrArrayLayers = 6; + } else { + wgpu_tex_desc.size.depthOrArrayLayers = (uint32_t) img->cmn.num_slices; + } + wgpu_tex_desc.format = _sg_wgpu_textureformat(img->cmn.pixel_format); + wgpu_tex_desc.mipLevelCount = (uint32_t) img->cmn.num_mipmaps; + wgpu_tex_desc.sampleCount = (uint32_t) img->cmn.sample_count; + img->wgpu.tex = wgpuDeviceCreateTexture(_sg.wgpu.dev, &wgpu_tex_desc); + if (0 == img->wgpu.tex) { + _SG_ERROR(WGPU_CREATE_TEXTURE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + if ((img->cmn.usage == SG_USAGE_IMMUTABLE) && !img->cmn.render_target) { + _sg_wgpu_copy_image_data(img, img->wgpu.tex, &desc->data); + } + WGPUTextureViewDescriptor wgpu_texview_desc; + _sg_clear(&wgpu_texview_desc, sizeof(wgpu_texview_desc)); + wgpu_texview_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_texview_desc.dimension = _sg_wgpu_texture_view_dimension(img->cmn.type); + wgpu_texview_desc.mipLevelCount = (uint32_t)img->cmn.num_mipmaps; + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + wgpu_texview_desc.arrayLayerCount = 6; + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + wgpu_texview_desc.arrayLayerCount = (uint32_t)img->cmn.num_slices; + } else { + wgpu_texview_desc.arrayLayerCount = 1; + } + if (_sg_is_depth_or_depth_stencil_format(img->cmn.pixel_format)) { + wgpu_texview_desc.aspect = WGPUTextureAspect_DepthOnly; + } else { + wgpu_texview_desc.aspect = WGPUTextureAspect_All; + } + img->wgpu.view = wgpuTextureCreateView(img->wgpu.tex, &wgpu_texview_desc); + if (0 == img->wgpu.view) { + _SG_ERROR(WGPU_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_image(_sg_image_t* img) { + SOKOL_ASSERT(img); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_IMAGE, img->slot.id); + if (img->wgpu.view) { + wgpuTextureViewRelease(img->wgpu.view); + img->wgpu.view = 0; + } + if (img->wgpu.tex) { + wgpuTextureRelease(img->wgpu.tex); + img->wgpu.tex = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && desc); + SOKOL_ASSERT(_sg.wgpu.dev); + const bool injected = (0 != desc->wgpu_sampler); + if (injected) { + smp->wgpu.smp = (WGPUSampler) desc->wgpu_sampler; + wgpuSamplerReference(smp->wgpu.smp); + } else { + WGPUSamplerDescriptor wgpu_desc; + _sg_clear(&wgpu_desc, sizeof(wgpu_desc)); + wgpu_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_desc.addressModeU = _sg_wgpu_sampler_address_mode(desc->wrap_u); + wgpu_desc.addressModeV = _sg_wgpu_sampler_address_mode(desc->wrap_v); + wgpu_desc.addressModeW = _sg_wgpu_sampler_address_mode(desc->wrap_w); + wgpu_desc.magFilter = _sg_wgpu_sampler_minmag_filter(desc->mag_filter); + wgpu_desc.minFilter = _sg_wgpu_sampler_minmag_filter(desc->min_filter); + wgpu_desc.mipmapFilter = _sg_wgpu_sampler_mipmap_filter(desc->mipmap_filter); + wgpu_desc.lodMinClamp = desc->min_lod; + wgpu_desc.lodMaxClamp = desc->max_lod; + wgpu_desc.compare = _sg_wgpu_comparefunc(desc->compare); + if (wgpu_desc.compare == WGPUCompareFunction_Never) { + wgpu_desc.compare = WGPUCompareFunction_Undefined; + } + wgpu_desc.maxAnisotropy = (uint16_t)desc->max_anisotropy; + smp->wgpu.smp = wgpuDeviceCreateSampler(_sg.wgpu.dev, &wgpu_desc); + if (0 == smp->wgpu.smp) { + _SG_ERROR(WGPU_CREATE_SAMPLER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_SAMPLER, smp->slot.id); + if (smp->wgpu.smp) { + wgpuSamplerRelease(smp->wgpu.smp); + smp->wgpu.smp = 0; + } +} + +_SOKOL_PRIVATE _sg_wgpu_shader_func_t _sg_wgpu_create_shader_func(const sg_shader_function* func, const char* label) { + SOKOL_ASSERT(func); + SOKOL_ASSERT(func->source); + SOKOL_ASSERT(func->entry); + + _sg_wgpu_shader_func_t res; + _sg_clear(&res, sizeof(res)); + _sg_strcpy(&res.entry, func->entry); + + WGPUShaderModuleWGSLDescriptor wgpu_shdmod_wgsl_desc; + _sg_clear(&wgpu_shdmod_wgsl_desc, sizeof(wgpu_shdmod_wgsl_desc)); + wgpu_shdmod_wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; + wgpu_shdmod_wgsl_desc.code = _sg_wgpu_stringview(func->source); + + WGPUShaderModuleDescriptor wgpu_shdmod_desc; + _sg_clear(&wgpu_shdmod_desc, sizeof(wgpu_shdmod_desc)); + wgpu_shdmod_desc.nextInChain = &wgpu_shdmod_wgsl_desc.chain; + wgpu_shdmod_desc.label = _sg_wgpu_stringview(label); + + // NOTE: if compilation fails we won't actually find out in this call since + // it always returns a valid module handle, and the GetCompilationInfo() call + // is asynchronous + res.module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc); + if (0 == res.module) { + _SG_ERROR(WGPU_CREATE_SHADER_MODULE_FAILED); + } + return res; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_shader_func(_sg_wgpu_shader_func_t* func) { + if (func->module) { + wgpuShaderModuleRelease(func->module); + func->module = 0; + } +} + +typedef struct { uint8_t sokol_slot, wgpu_slot; } _sg_wgpu_dynoffset_mapping_t; + +_SOKOL_PRIVATE int _sg_wgpu_dynoffset_cmp(const void* a, const void* b) { + const _sg_wgpu_dynoffset_mapping_t* aa = (const _sg_wgpu_dynoffset_mapping_t*)a; + const _sg_wgpu_dynoffset_mapping_t* bb = (const _sg_wgpu_dynoffset_mapping_t*)b; + if (aa->wgpu_slot < bb->wgpu_slot) return -1; + else if (aa->wgpu_slot > bb->wgpu_slot) return 1; + return 0; +} + +// NOTE: this is an out-of-range check for WGSL bindslots that's also active in release mode +_SOKOL_PRIVATE bool _sg_wgpu_ensure_wgsl_bindslot_ranges(const sg_shader_desc* desc) { + SOKOL_ASSERT(desc); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (desc->uniform_blocks[i].wgsl_group0_binding_n >= _SG_WGPU_MAX_UB_BINDGROUP_BIND_SLOTS) { + _SG_ERROR(WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (desc->storage_buffers[i].wgsl_group1_binding_n >= _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS) { + _SG_ERROR(WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (desc->images[i].wgsl_group1_binding_n >= _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS) { + _SG_ERROR(WGPU_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (desc->samplers[i].wgsl_group1_binding_n >= _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS) { + _SG_ERROR(WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + return false; + } + } + return true; +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(shd->wgpu.vertex_func.module == 0); + SOKOL_ASSERT(shd->wgpu.fragment_func.module == 0); + SOKOL_ASSERT(shd->wgpu.compute_func.module == 0); + SOKOL_ASSERT(shd->wgpu.bgl_ub == 0); + SOKOL_ASSERT(shd->wgpu.bg_ub == 0); + SOKOL_ASSERT(shd->wgpu.bgl_img_smp_sbuf == 0); + + // do a release-mode bounds-check on wgsl bindslots, even though out-of-range + // bindslots can't cause out-of-bounds accesses in the wgpu backend, this + // is done to be consistent with the other backends + if (!_sg_wgpu_ensure_wgsl_bindslot_ranges(desc)) { + return SG_RESOURCESTATE_FAILED; + } + + // build shader modules + bool shd_valid = true; + if (desc->vertex_func.source) { + shd->wgpu.vertex_func = _sg_wgpu_create_shader_func(&desc->vertex_func, desc->label); + shd_valid &= shd->wgpu.vertex_func.module != 0; + } + if (desc->fragment_func.source) { + shd->wgpu.fragment_func = _sg_wgpu_create_shader_func(&desc->fragment_func, desc->label); + shd_valid &= shd->wgpu.fragment_func.module != 0; + } + if (desc->compute_func.source) { + shd->wgpu.compute_func = _sg_wgpu_create_shader_func(&desc->compute_func, desc->label); + shd_valid &= shd->wgpu.compute_func.module != 0; + } + if (!shd_valid) { + _sg_wgpu_discard_shader_func(&shd->wgpu.vertex_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.fragment_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.compute_func); + return SG_RESOURCESTATE_FAILED; + } + + // create bind group layout and bind group for uniform blocks + // NOTE also need to create a mapping of sokol ub bind slots to array indices + // for the dynamic offsets array in the setBindGroup call + SOKOL_ASSERT(_SG_WGPU_MAX_UB_BINDGROUP_ENTRIES <= _SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES); + WGPUBindGroupLayoutEntry bgl_entries[_SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES]; + _sg_clear(bgl_entries, sizeof(bgl_entries)); + WGPUBindGroupLayoutDescriptor bgl_desc; + _sg_clear(&bgl_desc, sizeof(bgl_desc)); + WGPUBindGroupEntry bg_entries[_SG_WGPU_MAX_IMG_SMP_SBUF_BINDGROUP_ENTRIES]; + _sg_clear(&bg_entries, sizeof(bg_entries)); + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + _sg_wgpu_dynoffset_mapping_t dynoffset_map[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + _sg_clear(dynoffset_map, sizeof(dynoffset_map)); + size_t bgl_index = 0; + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.ub_grp0_bnd_n[i] = desc->uniform_blocks[i].wgsl_group0_binding_n; + WGPUBindGroupEntry* bg_entry = &bg_entries[bgl_index]; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.ub_grp0_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.uniform_blocks[i].stage); + bgl_entry->buffer.type = WGPUBufferBindingType_Uniform; + bgl_entry->buffer.hasDynamicOffset = true; + bg_entry->binding = bgl_entry->binding; + bg_entry->buffer = _sg.wgpu.uniform.buf; + bg_entry->size = _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE; + dynoffset_map[i].sokol_slot = i; + dynoffset_map[i].wgpu_slot = bgl_entry->binding; + bgl_index += 1; + } + bgl_desc.entryCount = bgl_index; + bgl_desc.entries = bgl_entries; + shd->wgpu.bgl_ub = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + SOKOL_ASSERT(shd->wgpu.bgl_ub); + bg_desc.layout = shd->wgpu.bgl_ub; + bg_desc.entryCount = bgl_index; + bg_desc.entries = bg_entries; + shd->wgpu.bg_ub = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + SOKOL_ASSERT(shd->wgpu.bg_ub); + + // sort the dynoffset_map by wgpu bindings, this is because the + // dynamic offsets of the WebGPU setBindGroup call must be in + // 'binding order', not 'bindgroup entry order' + qsort(dynoffset_map, bgl_index, sizeof(_sg_wgpu_dynoffset_mapping_t), _sg_wgpu_dynoffset_cmp); + shd->wgpu.ub_num_dynoffsets = bgl_index; + for (uint8_t i = 0; i < bgl_index; i++) { + const uint8_t sokol_slot = dynoffset_map[i].sokol_slot; + shd->wgpu.ub_dynoffsets[sokol_slot] = i; + } + + // create bind group layout for images, samplers and storage buffers + _sg_clear(bgl_entries, sizeof(bgl_entries)); + _sg_clear(&bgl_desc, sizeof(bgl_desc)); + bgl_index = 0; + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + const bool msaa = shd->cmn.images[i].multisampled; + shd->wgpu.img_grp1_bnd_n[i] = desc->images[i].wgsl_group1_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.img_grp1_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.images[i].stage); + bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(shd->cmn.images[i].image_type); + bgl_entry->texture.sampleType = _sg_wgpu_texture_sample_type(shd->cmn.images[i].sample_type, msaa); + bgl_entry->texture.multisampled = msaa; + bgl_index += 1; + } + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.smp_grp1_bnd_n[i] = desc->samplers[i].wgsl_group1_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.smp_grp1_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.samplers[i].stage); + bgl_entry->sampler.type = _sg_wgpu_sampler_binding_type(shd->cmn.samplers[i].sampler_type); + bgl_index += 1; + } + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + shd->wgpu.sbuf_grp1_bnd_n[i] = desc->storage_buffers[i].wgsl_group1_binding_n; + WGPUBindGroupLayoutEntry* bgl_entry = &bgl_entries[bgl_index]; + bgl_entry->binding = shd->wgpu.sbuf_grp1_bnd_n[i]; + bgl_entry->visibility = _sg_wgpu_shader_stage(shd->cmn.storage_buffers[i].stage); + if (shd->cmn.storage_buffers[i].readonly) { + bgl_entry->buffer.type = WGPUBufferBindingType_ReadOnlyStorage; + } else { + bgl_entry->buffer.type = WGPUBufferBindingType_Storage; + } + bgl_index += 1; + } + bgl_desc.entryCount = bgl_index; + bgl_desc.entries = bgl_entries; + shd->wgpu.bgl_img_smp_sbuf = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &bgl_desc); + if (shd->wgpu.bgl_img_smp_sbuf == 0) { + _SG_ERROR(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_wgpu_discard_shader_func(&shd->wgpu.vertex_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.fragment_func); + _sg_wgpu_discard_shader_func(&shd->wgpu.compute_func); + if (shd->wgpu.bgl_ub) { + wgpuBindGroupLayoutRelease(shd->wgpu.bgl_ub); + shd->wgpu.bgl_ub = 0; + } + if (shd->wgpu.bg_ub) { + wgpuBindGroupRelease(shd->wgpu.bg_ub); + shd->wgpu.bg_ub = 0; + } + if (shd->wgpu.bgl_img_smp_sbuf) { + wgpuBindGroupLayoutRelease(shd->wgpu.bgl_img_smp_sbuf); + shd->wgpu.bgl_img_smp_sbuf = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && shd && desc); + SOKOL_ASSERT(desc->shader.id == shd->slot.id); + SOKOL_ASSERT(shd->wgpu.bgl_ub); + SOKOL_ASSERT(shd->wgpu.bgl_img_smp_sbuf); + pip->shader = shd; + + pip->wgpu.blend_color.r = (double) desc->blend_color.r; + pip->wgpu.blend_color.g = (double) desc->blend_color.g; + pip->wgpu.blend_color.b = (double) desc->blend_color.b; + pip->wgpu.blend_color.a = (double) desc->blend_color.a; + + // - @group(0) for uniform blocks + // - @group(1) for all image, sampler and storagebuffer resources + WGPUBindGroupLayout wgpu_bgl[_SG_WGPU_NUM_BINDGROUPS]; + _sg_clear(&wgpu_bgl, sizeof(wgpu_bgl)); + wgpu_bgl[_SG_WGPU_UB_BINDGROUP_INDEX ] = shd->wgpu.bgl_ub; + wgpu_bgl[_SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX] = shd->wgpu.bgl_img_smp_sbuf; + WGPUPipelineLayoutDescriptor wgpu_pl_desc; + _sg_clear(&wgpu_pl_desc, sizeof(wgpu_pl_desc)); + wgpu_pl_desc.bindGroupLayoutCount = _SG_WGPU_NUM_BINDGROUPS; + wgpu_pl_desc.bindGroupLayouts = &wgpu_bgl[0]; + const WGPUPipelineLayout wgpu_pip_layout = wgpuDeviceCreatePipelineLayout(_sg.wgpu.dev, &wgpu_pl_desc); + if (0 == wgpu_pip_layout) { + _SG_ERROR(WGPU_CREATE_PIPELINE_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT(wgpu_pip_layout); + + if (pip->cmn.is_compute) { + WGPUComputePipelineDescriptor wgpu_pip_desc; + _sg_clear(&wgpu_pip_desc, sizeof(wgpu_pip_desc)); + wgpu_pip_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_pip_desc.layout = wgpu_pip_layout; + wgpu_pip_desc.compute.module = shd->wgpu.compute_func.module; + wgpu_pip_desc.compute.entryPoint = shd->wgpu.compute_func.entry.buf; + pip->wgpu.cpip = wgpuDeviceCreateComputePipeline(_sg.wgpu.dev, &wgpu_pip_desc); + wgpuPipelineLayoutRelease(wgpu_pip_layout); + if (0 == pip->wgpu.cpip) { + _SG_ERROR(WGPU_CREATE_COMPUTE_PIPELINE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } else { + WGPUVertexBufferLayout wgpu_vb_layouts[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + _sg_clear(wgpu_vb_layouts, sizeof(wgpu_vb_layouts)); + WGPUVertexAttribute wgpu_vtx_attrs[SG_MAX_VERTEXBUFFER_BINDSLOTS][SG_MAX_VERTEX_ATTRIBUTES]; + _sg_clear(wgpu_vtx_attrs, sizeof(wgpu_vtx_attrs)); + int wgpu_vb_num = 0; + for (int vb_idx = 0; vb_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS; vb_idx++, wgpu_vb_num++) { + const sg_vertex_buffer_layout_state* vbl_state = &desc->layout.buffers[vb_idx]; + if (0 == vbl_state->stride) { + break; + } + wgpu_vb_layouts[vb_idx].arrayStride = (uint64_t)vbl_state->stride; + wgpu_vb_layouts[vb_idx].stepMode = _sg_wgpu_stepmode(vbl_state->step_func); + wgpu_vb_layouts[vb_idx].attributes = &wgpu_vtx_attrs[vb_idx][0]; + } + for (int va_idx = 0; va_idx < SG_MAX_VERTEX_ATTRIBUTES; va_idx++) { + const sg_vertex_attr_state* va_state = &desc->layout.attrs[va_idx]; + if (SG_VERTEXFORMAT_INVALID == va_state->format) { + break; + } + const int vb_idx = va_state->buffer_index; + SOKOL_ASSERT(vb_idx < SG_MAX_VERTEXBUFFER_BINDSLOTS); + SOKOL_ASSERT(pip->cmn.vertex_buffer_layout_active[vb_idx]); + const size_t wgpu_attr_idx = wgpu_vb_layouts[vb_idx].attributeCount; + wgpu_vb_layouts[vb_idx].attributeCount += 1; + wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].format = _sg_wgpu_vertexformat(va_state->format); + wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].offset = (uint64_t)va_state->offset; + wgpu_vtx_attrs[vb_idx][wgpu_attr_idx].shaderLocation = (uint32_t)va_idx; + } + + WGPURenderPipelineDescriptor wgpu_pip_desc; + _sg_clear(&wgpu_pip_desc, sizeof(wgpu_pip_desc)); + WGPUDepthStencilState wgpu_ds_state; + _sg_clear(&wgpu_ds_state, sizeof(wgpu_ds_state)); + WGPUFragmentState wgpu_frag_state; + _sg_clear(&wgpu_frag_state, sizeof(wgpu_frag_state)); + WGPUColorTargetState wgpu_ctgt_state[SG_MAX_COLOR_ATTACHMENTS]; + _sg_clear(&wgpu_ctgt_state, sizeof(wgpu_ctgt_state)); + WGPUBlendState wgpu_blend_state[SG_MAX_COLOR_ATTACHMENTS]; + _sg_clear(&wgpu_blend_state, sizeof(wgpu_blend_state)); + wgpu_pip_desc.label = _sg_wgpu_stringview(desc->label); + wgpu_pip_desc.layout = wgpu_pip_layout; + wgpu_pip_desc.vertex.module = shd->wgpu.vertex_func.module; + wgpu_pip_desc.vertex.entryPoint = shd->wgpu.vertex_func.entry.buf; + wgpu_pip_desc.vertex.bufferCount = (size_t)wgpu_vb_num; + wgpu_pip_desc.vertex.buffers = &wgpu_vb_layouts[0]; + wgpu_pip_desc.primitive.topology = _sg_wgpu_topology(desc->primitive_type); + wgpu_pip_desc.primitive.stripIndexFormat = _sg_wgpu_stripindexformat(desc->primitive_type, desc->index_type); + wgpu_pip_desc.primitive.frontFace = _sg_wgpu_frontface(desc->face_winding); + wgpu_pip_desc.primitive.cullMode = _sg_wgpu_cullmode(desc->cull_mode); + if (SG_PIXELFORMAT_NONE != desc->depth.pixel_format) { + wgpu_ds_state.format = _sg_wgpu_textureformat(desc->depth.pixel_format); + wgpu_ds_state.depthWriteEnabled = _sg_wgpu_optional_bool(desc->depth.write_enabled); + wgpu_ds_state.depthCompare = _sg_wgpu_comparefunc(desc->depth.compare); + wgpu_ds_state.stencilFront.compare = _sg_wgpu_comparefunc(desc->stencil.front.compare); + wgpu_ds_state.stencilFront.failOp = _sg_wgpu_stencilop(desc->stencil.front.fail_op); + wgpu_ds_state.stencilFront.depthFailOp = _sg_wgpu_stencilop(desc->stencil.front.depth_fail_op); + wgpu_ds_state.stencilFront.passOp = _sg_wgpu_stencilop(desc->stencil.front.pass_op); + wgpu_ds_state.stencilBack.compare = _sg_wgpu_comparefunc(desc->stencil.back.compare); + wgpu_ds_state.stencilBack.failOp = _sg_wgpu_stencilop(desc->stencil.back.fail_op); + wgpu_ds_state.stencilBack.depthFailOp = _sg_wgpu_stencilop(desc->stencil.back.depth_fail_op); + wgpu_ds_state.stencilBack.passOp = _sg_wgpu_stencilop(desc->stencil.back.pass_op); + wgpu_ds_state.stencilReadMask = desc->stencil.read_mask; + wgpu_ds_state.stencilWriteMask = desc->stencil.write_mask; + wgpu_ds_state.depthBias = (int32_t)desc->depth.bias; + wgpu_ds_state.depthBiasSlopeScale = desc->depth.bias_slope_scale; + wgpu_ds_state.depthBiasClamp = desc->depth.bias_clamp; + wgpu_pip_desc.depthStencil = &wgpu_ds_state; + } + wgpu_pip_desc.multisample.count = (uint32_t)desc->sample_count; + wgpu_pip_desc.multisample.mask = 0xFFFFFFFF; + wgpu_pip_desc.multisample.alphaToCoverageEnabled = desc->alpha_to_coverage_enabled; + if (desc->color_count > 0) { + wgpu_frag_state.module = shd->wgpu.fragment_func.module; + wgpu_frag_state.entryPoint = shd->wgpu.fragment_func.entry.buf; + wgpu_frag_state.targetCount = (size_t)desc->color_count; + wgpu_frag_state.targets = &wgpu_ctgt_state[0]; + for (int i = 0; i < desc->color_count; i++) { + SOKOL_ASSERT(i < SG_MAX_COLOR_ATTACHMENTS); + wgpu_ctgt_state[i].format = _sg_wgpu_textureformat(desc->colors[i].pixel_format); + wgpu_ctgt_state[i].writeMask = _sg_wgpu_colorwritemask(desc->colors[i].write_mask); + if (desc->colors[i].blend.enabled) { + wgpu_ctgt_state[i].blend = &wgpu_blend_state[i]; + wgpu_blend_state[i].color.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_rgb); + wgpu_blend_state[i].color.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_rgb); + wgpu_blend_state[i].color.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_rgb); + wgpu_blend_state[i].alpha.operation = _sg_wgpu_blendop(desc->colors[i].blend.op_alpha); + wgpu_blend_state[i].alpha.srcFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.src_factor_alpha); + wgpu_blend_state[i].alpha.dstFactor = _sg_wgpu_blendfactor(desc->colors[i].blend.dst_factor_alpha); + } + } + wgpu_pip_desc.fragment = &wgpu_frag_state; + } + pip->wgpu.rpip = wgpuDeviceCreateRenderPipeline(_sg.wgpu.dev, &wgpu_pip_desc); + wgpuPipelineLayoutRelease(wgpu_pip_layout); + if (0 == pip->wgpu.rpip) { + _SG_ERROR(WGPU_CREATE_RENDER_PIPELINE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_wgpu_bindgroups_cache_invalidate(_SG_WGPU_BINDGROUPSCACHEITEMTYPE_PIPELINE, pip->slot.id); + if (pip == _sg.wgpu.cur_pipeline) { + _sg.wgpu.cur_pipeline = 0; + _sg.wgpu.cur_pipeline_id.id = SG_INVALID_ID; + } + if (pip->wgpu.rpip) { + wgpuRenderPipelineRelease(pip->wgpu.rpip); + pip->wgpu.rpip = 0; + } + if (pip->wgpu.cpip) { + wgpuComputePipelineRelease(pip->wgpu.cpip); + pip->wgpu.cpip = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_attachments(_sg_attachments_t* atts, _sg_image_t** color_images, _sg_image_t** resolve_images, _sg_image_t* ds_img, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && desc); + SOKOL_ASSERT(color_images && resolve_images); + + // copy image pointers and create renderable wgpu texture views + for (int i = 0; i < atts->cmn.num_colors; i++) { + const sg_attachment_desc* color_desc = &desc->colors[i]; + _SOKOL_UNUSED(color_desc); + SOKOL_ASSERT(color_desc->image.id != SG_INVALID_ID); + SOKOL_ASSERT(0 == atts->wgpu.colors[i].image); + SOKOL_ASSERT(color_images[i] && (color_images[i]->slot.id == color_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_color_format(color_images[i]->cmn.pixel_format)); + SOKOL_ASSERT(color_images[i]->wgpu.tex); + atts->wgpu.colors[i].image = color_images[i]; + + WGPUTextureViewDescriptor wgpu_color_view_desc; + _sg_clear(&wgpu_color_view_desc, sizeof(wgpu_color_view_desc)); + wgpu_color_view_desc.baseMipLevel = (uint32_t) color_desc->mip_level; + wgpu_color_view_desc.mipLevelCount = 1; + wgpu_color_view_desc.baseArrayLayer = (uint32_t) color_desc->slice; + wgpu_color_view_desc.arrayLayerCount = 1; + atts->wgpu.colors[i].view = wgpuTextureCreateView(color_images[i]->wgpu.tex, &wgpu_color_view_desc); + if (0 == atts->wgpu.colors[i].view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + + const sg_attachment_desc* resolve_desc = &desc->resolves[i]; + if (resolve_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(0 == atts->wgpu.resolves[i].image); + SOKOL_ASSERT(resolve_images[i] && (resolve_images[i]->slot.id == resolve_desc->image.id)); + SOKOL_ASSERT(color_images[i] && (color_images[i]->cmn.pixel_format == resolve_images[i]->cmn.pixel_format)); + SOKOL_ASSERT(resolve_images[i]->wgpu.tex); + atts->wgpu.resolves[i].image = resolve_images[i]; + + WGPUTextureViewDescriptor wgpu_resolve_view_desc; + _sg_clear(&wgpu_resolve_view_desc, sizeof(wgpu_resolve_view_desc)); + wgpu_resolve_view_desc.baseMipLevel = (uint32_t) resolve_desc->mip_level; + wgpu_resolve_view_desc.mipLevelCount = 1; + wgpu_resolve_view_desc.baseArrayLayer = (uint32_t) resolve_desc->slice; + wgpu_resolve_view_desc.arrayLayerCount = 1; + atts->wgpu.resolves[i].view = wgpuTextureCreateView(resolve_images[i]->wgpu.tex, &wgpu_resolve_view_desc); + if (0 == atts->wgpu.resolves[i].view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + } + SOKOL_ASSERT(0 == atts->wgpu.depth_stencil.image); + const sg_attachment_desc* ds_desc = &desc->depth_stencil; + if (ds_desc->image.id != SG_INVALID_ID) { + SOKOL_ASSERT(ds_img && (ds_img->slot.id == ds_desc->image.id)); + SOKOL_ASSERT(_sg_is_valid_rendertarget_depth_format(ds_img->cmn.pixel_format)); + SOKOL_ASSERT(ds_img->wgpu.tex); + atts->wgpu.depth_stencil.image = ds_img; + + WGPUTextureViewDescriptor wgpu_ds_view_desc; + _sg_clear(&wgpu_ds_view_desc, sizeof(wgpu_ds_view_desc)); + wgpu_ds_view_desc.baseMipLevel = (uint32_t) ds_desc->mip_level; + wgpu_ds_view_desc.mipLevelCount = 1; + wgpu_ds_view_desc.baseArrayLayer = (uint32_t) ds_desc->slice; + wgpu_ds_view_desc.arrayLayerCount = 1; + atts->wgpu.depth_stencil.view = wgpuTextureCreateView(ds_img->wgpu.tex, &wgpu_ds_view_desc); + if (0 == atts->wgpu.depth_stencil.view) { + _SG_ERROR(WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + for (int i = 0; i < atts->cmn.num_colors; i++) { + if (atts->wgpu.colors[i].view) { + wgpuTextureViewRelease(atts->wgpu.colors[i].view); + atts->wgpu.colors[i].view = 0; + } + if (atts->wgpu.resolves[i].view) { + wgpuTextureViewRelease(atts->wgpu.resolves[i].view); + atts->wgpu.resolves[i].view = 0; + } + } + if (atts->wgpu.depth_stencil.view) { + wgpuTextureViewRelease(atts->wgpu.depth_stencil.view); + atts->wgpu.depth_stencil.view = 0; + } +} + +_SOKOL_PRIVATE _sg_image_t* _sg_wgpu_attachments_color_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + // NOTE: may return null + return atts->wgpu.colors[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_wgpu_attachments_resolve_image(const _sg_attachments_t* atts, int index) { + SOKOL_ASSERT(atts && (index >= 0) && (index < SG_MAX_COLOR_ATTACHMENTS)); + // NOTE: may return null + return atts->wgpu.resolves[index].image; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_wgpu_attachments_ds_image(const _sg_attachments_t* atts) { + // NOTE: may return null + SOKOL_ASSERT(atts); + return atts->wgpu.depth_stencil.image; +} + +_SOKOL_PRIVATE void _sg_wgpu_init_color_att(WGPURenderPassColorAttachment* wgpu_att, const sg_color_attachment_action* action, WGPUTextureView color_view, WGPUTextureView resolve_view) { + wgpu_att->depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + wgpu_att->view = color_view; + wgpu_att->resolveTarget = resolve_view; + wgpu_att->loadOp = _sg_wgpu_load_op(color_view, action->load_action); + wgpu_att->storeOp = _sg_wgpu_store_op(color_view, action->store_action); + wgpu_att->clearValue.r = action->clear_value.r; + wgpu_att->clearValue.g = action->clear_value.g; + wgpu_att->clearValue.b = action->clear_value.b; + wgpu_att->clearValue.a = action->clear_value.a; +} + +_SOKOL_PRIVATE void _sg_wgpu_init_ds_att(WGPURenderPassDepthStencilAttachment* wgpu_att, const sg_pass_action* action, sg_pixel_format fmt, WGPUTextureView view) { + wgpu_att->view = view; + wgpu_att->depthLoadOp = _sg_wgpu_load_op(view, action->depth.load_action); + wgpu_att->depthStoreOp = _sg_wgpu_store_op(view, action->depth.store_action); + wgpu_att->depthClearValue = action->depth.clear_value; + wgpu_att->depthReadOnly = false; + if (_sg_is_depth_stencil_format(fmt)) { + wgpu_att->stencilLoadOp = _sg_wgpu_load_op(view, action->stencil.load_action); + wgpu_att->stencilStoreOp = _sg_wgpu_store_op(view, action->stencil.store_action); + } else { + wgpu_att->stencilLoadOp = WGPULoadOp_Undefined; + wgpu_att->stencilStoreOp = WGPUStoreOp_Undefined; + } + wgpu_att->stencilClearValue = action->stencil.clear_value; + wgpu_att->stencilReadOnly = false; +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_compute_pass(const sg_pass* pass) { + WGPUComputePassDescriptor wgpu_pass_desc; + _sg_clear(&wgpu_pass_desc, sizeof(wgpu_pass_desc)); + wgpu_pass_desc.label = _sg_wgpu_stringview(pass->label); + _sg.wgpu.cpass_enc = wgpuCommandEncoderBeginComputePass(_sg.wgpu.cmd_enc, &wgpu_pass_desc); + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + // clear initial bindings + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_UB_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_render_pass(const sg_pass* pass) { + const _sg_attachments_t* atts = _sg.cur_pass.atts; + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + WGPURenderPassDescriptor wgpu_pass_desc; + WGPURenderPassColorAttachment wgpu_color_att[SG_MAX_COLOR_ATTACHMENTS]; + WGPURenderPassDepthStencilAttachment wgpu_ds_att; + _sg_clear(&wgpu_pass_desc, sizeof(wgpu_pass_desc)); + _sg_clear(&wgpu_color_att, sizeof(wgpu_color_att)); + _sg_clear(&wgpu_ds_att, sizeof(wgpu_ds_att)); + wgpu_pass_desc.label = _sg_wgpu_stringview(pass->label); + if (atts) { + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_VALID); + for (int i = 0; i < atts->cmn.num_colors; i++) { + _sg_wgpu_init_color_att(&wgpu_color_att[i], &action->colors[i], atts->wgpu.colors[i].view, atts->wgpu.resolves[i].view); + } + wgpu_pass_desc.colorAttachmentCount = (size_t)atts->cmn.num_colors; + wgpu_pass_desc.colorAttachments = &wgpu_color_att[0]; + if (atts->wgpu.depth_stencil.image) { + _sg_wgpu_init_ds_att(&wgpu_ds_att, action, atts->wgpu.depth_stencil.image->cmn.pixel_format, atts->wgpu.depth_stencil.view); + wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att; + } + } else { + WGPUTextureView wgpu_color_view = (WGPUTextureView) swapchain->wgpu.render_view; + WGPUTextureView wgpu_resolve_view = (WGPUTextureView) swapchain->wgpu.resolve_view; + WGPUTextureView wgpu_depth_stencil_view = (WGPUTextureView) swapchain->wgpu.depth_stencil_view; + _sg_wgpu_init_color_att(&wgpu_color_att[0], &action->colors[0], wgpu_color_view, wgpu_resolve_view); + wgpu_pass_desc.colorAttachmentCount = 1; + wgpu_pass_desc.colorAttachments = &wgpu_color_att[0]; + if (wgpu_depth_stencil_view) { + SOKOL_ASSERT(swapchain->depth_format > SG_PIXELFORMAT_NONE); + _sg_wgpu_init_ds_att(&wgpu_ds_att, action, swapchain->depth_format, wgpu_depth_stencil_view); + wgpu_pass_desc.depthStencilAttachment = &wgpu_ds_att; + } + } + _sg.wgpu.rpass_enc = wgpuCommandEncoderBeginRenderPass(_sg.wgpu.cmd_enc, &wgpu_pass_desc); + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_UB_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, _SG_WGPU_IMG_SMP_SBUF_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); +} + +_SOKOL_PRIVATE void _sg_wgpu_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + SOKOL_ASSERT(_sg.wgpu.dev); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + SOKOL_ASSERT(0 == _sg.wgpu.rpass_enc); + SOKOL_ASSERT(0 == _sg.wgpu.cpass_enc); + + _sg.wgpu.cur_pipeline = 0; + _sg.wgpu.cur_pipeline_id.id = SG_INVALID_ID; + _sg_wgpu_bindings_cache_clear(); + + if (pass->compute) { + _sg_wgpu_begin_compute_pass(pass); + } else { + _sg_wgpu_begin_render_pass(pass); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_end_pass(void) { + if (_sg.wgpu.rpass_enc) { + wgpuRenderPassEncoderEnd(_sg.wgpu.rpass_enc); + wgpuRenderPassEncoderRelease(_sg.wgpu.rpass_enc); + _sg.wgpu.rpass_enc = 0; + } + if (_sg.wgpu.cpass_enc) { + wgpuComputePassEncoderEnd(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderRelease(_sg.wgpu.cpass_enc); + _sg.wgpu.cpass_enc = 0; + } +} + +_SOKOL_PRIVATE void _sg_wgpu_commit(void) { + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + + _sg_wgpu_uniform_buffer_on_commit(); + + WGPUCommandBufferDescriptor cmd_buf_desc; + _sg_clear(&cmd_buf_desc, sizeof(cmd_buf_desc)); + WGPUCommandBuffer wgpu_cmd_buf = wgpuCommandEncoderFinish(_sg.wgpu.cmd_enc, &cmd_buf_desc); + SOKOL_ASSERT(wgpu_cmd_buf); + wgpuCommandEncoderRelease(_sg.wgpu.cmd_enc); + _sg.wgpu.cmd_enc = 0; + + wgpuQueueSubmit(_sg.wgpu.queue, 1, &wgpu_cmd_buf); + wgpuCommandBufferRelease(wgpu_cmd_buf); + + // create a new render-command-encoder for next frame + WGPUCommandEncoderDescriptor cmd_enc_desc; + _sg_clear(&cmd_enc_desc, sizeof(cmd_enc_desc)); + _sg.wgpu.cmd_enc = wgpuDeviceCreateCommandEncoder(_sg.wgpu.dev, &cmd_enc_desc); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + // FIXME FIXME FIXME: CLIPPING THE VIEWPORT HERE IS WRONG!!! + // (but currently required because WebGPU insists that the viewport rectangle must be + // fully contained inside the framebuffer, but this doesn't make any sense, and also + // isn't required by the backend APIs) + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.width, _sg.cur_pass.height); + float xf = (float) clip.x; + float yf = (float) (origin_top_left ? clip.y : (_sg.cur_pass.height - (clip.y + clip.h))); + float wf = (float) clip.w; + float hf = (float) clip.h; + wgpuRenderPassEncoderSetViewport(_sg.wgpu.rpass_enc, xf, yf, wf, hf, 0.0f, 1.0f); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + const _sg_recti_t clip = _sg_clipi(x, y, w, h, _sg.cur_pass.width, _sg.cur_pass.height); + uint32_t sx = (uint32_t) clip.x; + uint32_t sy = (uint32_t) (origin_top_left ? clip.y : (_sg.cur_pass.height - (clip.y + clip.h))); + uint32_t sw = (uint32_t) clip.w; + uint32_t sh = (uint32_t) clip.h; + wgpuRenderPassEncoderSetScissorRect(_sg.wgpu.rpass_enc, sx, sy, sw, sh); +} + +_SOKOL_PRIVATE void _sg_wgpu_set_ub_bindgroup(const _sg_shader_t* shd) { + // NOTE: dynamic offsets must be in binding order, not in BindGroupEntry order + SOKOL_ASSERT(shd->wgpu.ub_num_dynoffsets < SG_MAX_UNIFORMBLOCK_BINDSLOTS); + uint32_t dyn_offsets[SG_MAX_UNIFORMBLOCK_BINDSLOTS]; + _sg_clear(dyn_offsets, sizeof(dyn_offsets)); + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + if (shd->cmn.uniform_blocks[i].stage == SG_SHADERSTAGE_NONE) { + continue; + } + uint8_t dynoffset_index = shd->wgpu.ub_dynoffsets[i]; + SOKOL_ASSERT(dynoffset_index < shd->wgpu.ub_num_dynoffsets); + dyn_offsets[dynoffset_index] = _sg.wgpu.uniform.bind_offsets[i]; + } + if (_sg.cur_pass.is_compute) { + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderSetBindGroup(_sg.wgpu.cpass_enc, + _SG_WGPU_UB_BINDGROUP_INDEX, + shd->wgpu.bg_ub, + shd->wgpu.ub_num_dynoffsets, + dyn_offsets); + } else { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.rpass_enc, + _SG_WGPU_UB_BINDGROUP_INDEX, + shd->wgpu.bg_ub, + shd->wgpu.ub_num_dynoffsets, + dyn_offsets); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); + _sg.wgpu.cur_pipeline = pip; + _sg.wgpu.cur_pipeline_id.id = pip->slot.id; + if (pip->cmn.is_compute) { + SOKOL_ASSERT(_sg.cur_pass.is_compute); + SOKOL_ASSERT(pip->wgpu.cpip); + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderSetPipeline(_sg.wgpu.cpass_enc, pip->wgpu.cpip); + } else { + SOKOL_ASSERT(!_sg.cur_pass.is_compute); + SOKOL_ASSERT(pip->wgpu.rpip); + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + _sg.wgpu.use_indexed_draw = (pip->cmn.index_type != SG_INDEXTYPE_NONE); + wgpuRenderPassEncoderSetPipeline(_sg.wgpu.rpass_enc, pip->wgpu.rpip); + wgpuRenderPassEncoderSetBlendConstant(_sg.wgpu.rpass_enc, &pip->wgpu.blend_color); + wgpuRenderPassEncoderSetStencilReference(_sg.wgpu.rpass_enc, pip->cmn.stencil.ref); + } + // bind groups must be set because pipelines without uniform blocks or resource bindings + // will still create 'empty' BindGroupLayouts + _sg_wgpu_set_ub_bindgroup(pip->shader); + _sg_wgpu_set_img_smp_sbuf_bindgroup(0); // this will set the 'empty bind group' +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip->shader && (bnd->pip->cmn.shader_id.id == bnd->pip->shader->slot.id)); + bool retval = true; + if (!_sg.cur_pass.is_compute) { + retval &= _sg_wgpu_apply_index_buffer(bnd); + retval &= _sg_wgpu_apply_vertex_buffers(bnd); + } + retval &= _sg_wgpu_apply_bindgroup(bnd); + return retval; +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_uniforms(int ub_slot, const sg_range* data) { + const uint32_t alignment = _sg.wgpu.limits.limits.minUniformBufferOffsetAlignment; + SOKOL_ASSERT(_sg.wgpu.uniform.staging); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT((_sg.wgpu.uniform.offset + data->size) <= _sg.wgpu.uniform.num_bytes); + SOKOL_ASSERT((_sg.wgpu.uniform.offset & (alignment - 1)) == 0); + const _sg_pipeline_t* pip = _sg.wgpu.cur_pipeline; + SOKOL_ASSERT(pip && pip->shader); + SOKOL_ASSERT(pip->slot.id == _sg.wgpu.cur_pipeline_id.id); + const _sg_shader_t* shd = pip->shader; + SOKOL_ASSERT(shd->slot.id == pip->cmn.shader_id.id); + SOKOL_ASSERT(data->size == shd->cmn.uniform_blocks[ub_slot].size); + SOKOL_ASSERT(data->size <= _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE); + + _sg_stats_add(wgpu.uniforms.num_set_bindgroup, 1); + memcpy(_sg.wgpu.uniform.staging + _sg.wgpu.uniform.offset, data->ptr, data->size); + _sg.wgpu.uniform.bind_offsets[ub_slot] = _sg.wgpu.uniform.offset; + _sg.wgpu.uniform.offset = _sg_roundup_u32(_sg.wgpu.uniform.offset + (uint32_t)data->size, alignment); + + _sg_wgpu_set_ub_bindgroup(shd); +} + +_SOKOL_PRIVATE void _sg_wgpu_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.wgpu.rpass_enc); + SOKOL_ASSERT(_sg.wgpu.cur_pipeline && (_sg.wgpu.cur_pipeline->slot.id == _sg.wgpu.cur_pipeline_id.id)); + if (SG_INDEXTYPE_NONE != _sg.wgpu.cur_pipeline->cmn.index_type) { + wgpuRenderPassEncoderDrawIndexed(_sg.wgpu.rpass_enc, (uint32_t)num_elements, (uint32_t)num_instances, (uint32_t)base_element, 0, 0); + } else { + wgpuRenderPassEncoderDraw(_sg.wgpu.rpass_enc, (uint32_t)num_elements, (uint32_t)num_instances, (uint32_t)base_element, 0); + } +} + +_SOKOL_PRIVATE void _sg_wgpu_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + SOKOL_ASSERT(_sg.wgpu.cpass_enc); + wgpuComputePassEncoderDispatchWorkgroups(_sg.wgpu.cpass_enc, + (uint32_t)num_groups_x, + (uint32_t)num_groups_y, + (uint32_t)num_groups_z); +} + +_SOKOL_PRIVATE void _sg_wgpu_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + SOKOL_ASSERT(buf); + _sg_wgpu_copy_buffer_data(buf, 0, data); +} + +_SOKOL_PRIVATE void _sg_wgpu_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + _SOKOL_UNUSED(new_frame); + _sg_wgpu_copy_buffer_data(buf, (uint64_t)buf->cmn.append_pos, data); +} + +_SOKOL_PRIVATE void _sg_wgpu_update_image(_sg_image_t* img, const sg_image_data* data) { + SOKOL_ASSERT(img && data); + _sg_wgpu_copy_image_data(img, img->wgpu.tex, data); +} +#endif + +// ██████ ███████ ███ ██ ███████ ██████ ██ ██████ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ███ █████ ██ ██ ██ █████ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ███████ ██ ████ ███████ ██ ██ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>generic backend +static inline void _sg_setup_backend(const sg_desc* desc) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_setup_backend(desc); + #elif defined(SOKOL_METAL) + _sg_mtl_setup_backend(desc); + #elif defined(SOKOL_D3D11) + _sg_d3d11_setup_backend(desc); + #elif defined(SOKOL_WGPU) + _sg_wgpu_setup_backend(desc); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_setup_backend(desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_backend(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_backend(); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_backend(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_backend(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_backend(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_backend(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_reset_state_cache(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_reset_state_cache(); + #elif defined(SOKOL_METAL) + _sg_mtl_reset_state_cache(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_reset_state_cache(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_reset_state_cache(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_reset_state_cache(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_buffer(buf, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_buffer(buf, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_buffer(buf, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_buffer(buf, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_buffer(buf, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_buffer(_sg_buffer_t* buf) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_buffer(buf); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_buffer(buf); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_buffer(buf); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_buffer(buf); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_buffer(buf); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_image(_sg_image_t* img, const sg_image_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_image(img, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_image(img, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_image(img, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_image(img, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_image(img, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_image(_sg_image_t* img) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_image(img); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_image(img); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_image(img); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_image(img); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_image(img); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_sampler(smp, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_sampler(smp, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_sampler(smp, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_sampler(smp, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_sampler(smp, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_sampler(_sg_sampler_t* smp) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_sampler(smp); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_sampler(smp); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_sampler(smp); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_sampler(smp); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_sampler(smp); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_shader(shd, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_shader(shd, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_shader(shd, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_shader(shd, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_shader(shd, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_shader(_sg_shader_t* shd) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_shader(shd); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_shader(shd); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_shader(shd); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_shader(shd); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_shader(shd); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_pipeline(_sg_pipeline_t* pip, _sg_shader_t* shd, const sg_pipeline_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_pipeline(pip, shd, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_pipeline(pip, shd, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_pipeline(pip, shd, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_pipeline(pip, shd, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_pipeline(pip, shd, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_pipeline(_sg_pipeline_t* pip) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_pipeline(pip); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_pipeline(pip); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_pipeline(pip); + #elif defined(SOKOL_WGPU) + _sg_wgpu_discard_pipeline(pip); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_pipeline(pip); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline sg_resource_state _sg_create_attachments(_sg_attachments_t* atts, _sg_image_t** color_images, _sg_image_t** resolve_images, _sg_image_t* ds_image, const sg_attachments_desc* desc) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_create_attachments(atts, color_images, resolve_images, ds_image, desc); + #elif defined(SOKOL_METAL) + return _sg_mtl_create_attachments(atts, color_images, resolve_images, ds_image, desc); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_create_attachments(atts, color_images, resolve_images, ds_image, desc); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_create_attachments(atts, color_images, resolve_images, ds_image, desc); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_create_attachments(atts, color_images, resolve_images, ds_image, desc); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_discard_attachments(_sg_attachments_t* atts) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_discard_attachments(atts); + #elif defined(SOKOL_METAL) + _sg_mtl_discard_attachments(atts); + #elif defined(SOKOL_D3D11) + _sg_d3d11_discard_attachments(atts); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_discard_attachments(atts); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_discard_attachments(atts); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline _sg_image_t* _sg_attachments_color_image(const _sg_attachments_t* atts, int index) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_attachments_color_image(atts, index); + #elif defined(SOKOL_METAL) + return _sg_mtl_attachments_color_image(atts, index); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_attachments_color_image(atts, index); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_attachments_color_image(atts, index); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_attachments_color_image(atts, index); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline _sg_image_t* _sg_attachments_resolve_image(const _sg_attachments_t* atts, int index) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_attachments_resolve_image(atts, index); + #elif defined(SOKOL_METAL) + return _sg_mtl_attachments_resolve_image(atts, index); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_attachments_resolve_image(atts, index); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_attachments_resolve_image(atts, index); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_attachments_resolve_image(atts, index); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline _sg_image_t* _sg_attachments_ds_image(const _sg_attachments_t* atts) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_attachments_ds_image(atts); + #elif defined(SOKOL_METAL) + return _sg_mtl_attachments_ds_image(atts); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_attachments_ds_image(atts); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_attachments_ds_image(atts); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_attachments_ds_image(atts); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_begin_pass(const sg_pass* pass) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_begin_pass(pass); + #elif defined(SOKOL_METAL) + _sg_mtl_begin_pass(pass); + #elif defined(SOKOL_D3D11) + _sg_d3d11_begin_pass(pass); + #elif defined(SOKOL_WGPU) + _sg_wgpu_begin_pass(pass); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_begin_pass(pass); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_end_pass(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_end_pass(); + #elif defined(SOKOL_METAL) + _sg_mtl_end_pass(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_end_pass(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_end_pass(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_end_pass(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_viewport(int x, int y, int w, int h, bool origin_top_left) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_viewport(x, y, w, h, origin_top_left); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_viewport(x, y, w, h, origin_top_left); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_scissor_rect(x, y, w, h, origin_top_left); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_scissor_rect(x, y, w, h, origin_top_left); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_pipeline(_sg_pipeline_t* pip) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_pipeline(pip); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_pipeline(pip); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_pipeline(pip); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_pipeline(pip); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_pipeline(pip); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline bool _sg_apply_bindings(_sg_bindings_t* bnd) { + #if defined(_SOKOL_ANY_GL) + return _sg_gl_apply_bindings(bnd); + #elif defined(SOKOL_METAL) + return _sg_mtl_apply_bindings(bnd); + #elif defined(SOKOL_D3D11) + return _sg_d3d11_apply_bindings(bnd); + #elif defined(SOKOL_WGPU) + return _sg_wgpu_apply_bindings(bnd); + #elif defined(SOKOL_DUMMY_BACKEND) + return _sg_dummy_apply_bindings(bnd); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_apply_uniforms(int ub_slot, const sg_range* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_uniforms(ub_slot, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_uniforms(ub_slot, data); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_draw(int base_element, int num_elements, int num_instances) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_METAL) + _sg_mtl_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_D3D11) + _sg_d3d11_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_WGPU) + _sg_wgpu_draw(base_element, num_elements, num_instances); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_draw(base_element, num_elements, num_instances); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_METAL) + _sg_mtl_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_D3D11) + _sg_d3d11_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_WGPU) + _sg_wgpu_dispatch(num_groups_x, num_groups_y, num_groups_z); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_dispatch(num_groups_x, num_groups_y, num_groups_z); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_commit(void) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_commit(); + #elif defined(SOKOL_METAL) + _sg_mtl_commit(); + #elif defined(SOKOL_D3D11) + _sg_d3d11_commit(); + #elif defined(SOKOL_WGPU) + _sg_wgpu_commit(); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_commit(); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_update_buffer(_sg_buffer_t* buf, const sg_range* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_update_buffer(buf, data); + #elif defined(SOKOL_METAL) + _sg_mtl_update_buffer(buf, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_update_buffer(buf, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_update_buffer(buf, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_update_buffer(buf, data); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_append_buffer(_sg_buffer_t* buf, const sg_range* data, bool new_frame) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_METAL) + _sg_mtl_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_D3D11) + _sg_d3d11_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_WGPU) + _sg_wgpu_append_buffer(buf, data, new_frame); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_append_buffer(buf, data, new_frame); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_update_image(_sg_image_t* img, const sg_image_data* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_update_image(img, data); + #elif defined(SOKOL_METAL) + _sg_mtl_update_image(img, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_update_image(img, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_update_image(img, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_update_image(img, data); + #else + #error("INVALID BACKEND"); + #endif +} + +static inline void _sg_push_debug_group(const char* name) { + #if defined(SOKOL_METAL) + _sg_mtl_push_debug_group(name); + #else + _SOKOL_UNUSED(name); + #endif +} + +static inline void _sg_pop_debug_group(void) { + #if defined(SOKOL_METAL) + _sg_mtl_pop_debug_group(); + #endif +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +_SOKOL_PRIVATE void _sg_pool_init(_sg_pool_t* pool, int num) { + SOKOL_ASSERT(pool && (num >= 1)); + // slot 0 is reserved for the 'invalid id', so bump the pool size by 1 + pool->size = num + 1; + pool->queue_top = 0; + // generation counters indexable by pool slot index, slot 0 is reserved + size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; + pool->gen_ctrs = (uint32_t*)_sg_malloc_clear(gen_ctrs_size); + // it's not a bug to only reserve 'num' here + pool->free_queue = (int*) _sg_malloc_clear(sizeof(int) * (size_t)num); + // never allocate the zero-th pool item since the invalid id is 0 + for (int i = pool->size-1; i >= 1; i--) { + pool->free_queue[pool->queue_top++] = i; + } +} + +_SOKOL_PRIVATE void _sg_pool_discard(_sg_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _sg_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _sg_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +_SOKOL_PRIVATE int _sg_pool_alloc_index(_sg_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + if (pool->queue_top > 0) { + int slot_index = pool->free_queue[--pool->queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return slot_index; + } else { + // pool exhausted + return _SG_INVALID_SLOT_INDEX; + } +} + +_SOKOL_PRIVATE void _sg_pool_free_index(_sg_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + SOKOL_ASSERT(pool->queue_top < pool->size); + #ifdef SOKOL_DEBUG + // debug check against double-free + for (int i = 0; i < pool->queue_top; i++) { + SOKOL_ASSERT(pool->free_queue[i] != slot_index); + } + #endif + pool->free_queue[pool->queue_top++] = slot_index; + SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +_SOKOL_PRIVATE void _sg_slot_reset(_sg_slot_t* slot) { + SOKOL_ASSERT(slot); + _sg_clear(slot, sizeof(_sg_slot_t)); +} + +_SOKOL_PRIVATE void _sg_reset_buffer_to_alloc_state(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf); + _sg_slot_t slot = buf->slot; + _sg_clear(buf, sizeof(*buf)); + buf->slot = slot; + buf->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_image_to_alloc_state(_sg_image_t* img) { + SOKOL_ASSERT(img); + _sg_slot_t slot = img->slot; + _sg_clear(img, sizeof(*img)); + img->slot = slot; + img->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_sampler_to_alloc_state(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp); + _sg_slot_t slot = smp->slot; + _sg_clear(smp, sizeof(*smp)); + smp->slot = slot; + smp->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_shader_to_alloc_state(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + _sg_slot_t slot = shd->slot; + _sg_clear(shd, sizeof(*shd)); + shd->slot = slot; + shd->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_pipeline_to_alloc_state(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sg_slot_t slot = pip->slot; + _sg_clear(pip, sizeof(*pip)); + pip->slot = slot; + pip->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_reset_attachments_to_alloc_state(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + _sg_slot_t slot = atts->slot; + _sg_clear(atts, sizeof(*atts)); + atts->slot = slot; + atts->slot.state = SG_RESOURCESTATE_ALLOC; +} + +_SOKOL_PRIVATE void _sg_setup_pools(_sg_pools_t* p, const sg_desc* desc) { + SOKOL_ASSERT(p); + SOKOL_ASSERT(desc); + // note: the pools here will have an additional item, since slot 0 is reserved + SOKOL_ASSERT((desc->buffer_pool_size > 0) && (desc->buffer_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->buffer_pool, desc->buffer_pool_size); + size_t buffer_pool_byte_size = sizeof(_sg_buffer_t) * (size_t)p->buffer_pool.size; + p->buffers = (_sg_buffer_t*) _sg_malloc_clear(buffer_pool_byte_size); + + SOKOL_ASSERT((desc->image_pool_size > 0) && (desc->image_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->image_pool, desc->image_pool_size); + size_t image_pool_byte_size = sizeof(_sg_image_t) * (size_t)p->image_pool.size; + p->images = (_sg_image_t*) _sg_malloc_clear(image_pool_byte_size); + + SOKOL_ASSERT((desc->sampler_pool_size > 0) && (desc->sampler_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->sampler_pool, desc->sampler_pool_size); + size_t sampler_pool_byte_size = sizeof(_sg_sampler_t) * (size_t)p->sampler_pool.size; + p->samplers = (_sg_sampler_t*) _sg_malloc_clear(sampler_pool_byte_size); + + SOKOL_ASSERT((desc->shader_pool_size > 0) && (desc->shader_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->shader_pool, desc->shader_pool_size); + size_t shader_pool_byte_size = sizeof(_sg_shader_t) * (size_t)p->shader_pool.size; + p->shaders = (_sg_shader_t*) _sg_malloc_clear(shader_pool_byte_size); + + SOKOL_ASSERT((desc->pipeline_pool_size > 0) && (desc->pipeline_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->pipeline_pool, desc->pipeline_pool_size); + size_t pipeline_pool_byte_size = sizeof(_sg_pipeline_t) * (size_t)p->pipeline_pool.size; + p->pipelines = (_sg_pipeline_t*) _sg_malloc_clear(pipeline_pool_byte_size); + + SOKOL_ASSERT((desc->attachments_pool_size > 0) && (desc->attachments_pool_size < _SG_MAX_POOL_SIZE)); + _sg_pool_init(&p->attachments_pool, desc->attachments_pool_size); + size_t attachments_pool_byte_size = sizeof(_sg_attachments_t) * (size_t)p->attachments_pool.size; + p->attachments = (_sg_attachments_t*) _sg_malloc_clear(attachments_pool_byte_size); +} + +_SOKOL_PRIVATE void _sg_discard_pools(_sg_pools_t* p) { + SOKOL_ASSERT(p); + _sg_free(p->attachments); p->attachments = 0; + _sg_free(p->pipelines); p->pipelines = 0; + _sg_free(p->shaders); p->shaders = 0; + _sg_free(p->samplers); p->samplers = 0; + _sg_free(p->images); p->images = 0; + _sg_free(p->buffers); p->buffers = 0; + _sg_pool_discard(&p->attachments_pool); + _sg_pool_discard(&p->pipeline_pool); + _sg_pool_discard(&p->shader_pool); + _sg_pool_discard(&p->sampler_pool); + _sg_pool_discard(&p->image_pool); + _sg_pool_discard(&p->buffer_pool); +} + +/* allocate the slot at slot_index: + - bump the slot's generation counter + - create a resource id from the generation counter and slot index + - set the slot's id to this id + - set the slot's state to ALLOC + - return the resource id +*/ +_SOKOL_PRIVATE uint32_t _sg_slot_alloc(_sg_pool_t* pool, _sg_slot_t* slot, int slot_index) { + /* FIXME: add handling for an overflowing generation counter, + for now, just overflow (another option is to disable + the slot) + */ + SOKOL_ASSERT(pool && pool->gen_ctrs); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(slot->id == SG_INVALID_ID); + SOKOL_ASSERT(slot->state == SG_RESOURCESTATE_INITIAL); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SG_SLOT_SHIFT)|(slot_index & _SG_SLOT_MASK); + slot->state = SG_RESOURCESTATE_ALLOC; + return slot->id; +} + +// extract slot index from id +_SOKOL_PRIVATE int _sg_slot_index(uint32_t id) { + int slot_index = (int) (id & _SG_SLOT_MASK); + SOKOL_ASSERT(_SG_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +// returns pointer to resource by id without matching id check +_SOKOL_PRIVATE _sg_buffer_t* _sg_buffer_at(const _sg_pools_t* p, uint32_t buf_id) { + SOKOL_ASSERT(p && (SG_INVALID_ID != buf_id)); + int slot_index = _sg_slot_index(buf_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->buffer_pool.size)); + return &p->buffers[slot_index]; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_image_at(const _sg_pools_t* p, uint32_t img_id) { + SOKOL_ASSERT(p && (SG_INVALID_ID != img_id)); + int slot_index = _sg_slot_index(img_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->image_pool.size)); + return &p->images[slot_index]; +} + +_SOKOL_PRIVATE _sg_sampler_t* _sg_sampler_at(const _sg_pools_t* p, uint32_t smp_id) { + SOKOL_ASSERT(p && (SG_INVALID_ID != smp_id)); + int slot_index = _sg_slot_index(smp_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->sampler_pool.size)); + return &p->samplers[slot_index]; +} + +_SOKOL_PRIVATE _sg_shader_t* _sg_shader_at(const _sg_pools_t* p, uint32_t shd_id) { + SOKOL_ASSERT(p && (SG_INVALID_ID != shd_id)); + int slot_index = _sg_slot_index(shd_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->shader_pool.size)); + return &p->shaders[slot_index]; +} + +_SOKOL_PRIVATE _sg_pipeline_t* _sg_pipeline_at(const _sg_pools_t* p, uint32_t pip_id) { + SOKOL_ASSERT(p && (SG_INVALID_ID != pip_id)); + int slot_index = _sg_slot_index(pip_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->pipeline_pool.size)); + return &p->pipelines[slot_index]; +} + +_SOKOL_PRIVATE _sg_attachments_t* _sg_attachments_at(const _sg_pools_t* p, uint32_t atts_id) { + SOKOL_ASSERT(p && (SG_INVALID_ID != atts_id)); + int slot_index = _sg_slot_index(atts_id); + SOKOL_ASSERT((slot_index > _SG_INVALID_SLOT_INDEX) && (slot_index < p->attachments_pool.size)); + return &p->attachments[slot_index]; +} + +// returns pointer to resource with matching id check, may return 0 +_SOKOL_PRIVATE _sg_buffer_t* _sg_lookup_buffer(const _sg_pools_t* p, uint32_t buf_id) { + if (SG_INVALID_ID != buf_id) { + _sg_buffer_t* buf = _sg_buffer_at(p, buf_id); + if (buf->slot.id == buf_id) { + return buf; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_image_t* _sg_lookup_image(const _sg_pools_t* p, uint32_t img_id) { + if (SG_INVALID_ID != img_id) { + _sg_image_t* img = _sg_image_at(p, img_id); + if (img->slot.id == img_id) { + return img; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_sampler_t* _sg_lookup_sampler(const _sg_pools_t* p, uint32_t smp_id) { + if (SG_INVALID_ID != smp_id) { + _sg_sampler_t* smp = _sg_sampler_at(p, smp_id); + if (smp->slot.id == smp_id) { + return smp; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_shader_t* _sg_lookup_shader(const _sg_pools_t* p, uint32_t shd_id) { + SOKOL_ASSERT(p); + if (SG_INVALID_ID != shd_id) { + _sg_shader_t* shd = _sg_shader_at(p, shd_id); + if (shd->slot.id == shd_id) { + return shd; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_pipeline_t* _sg_lookup_pipeline(const _sg_pools_t* p, uint32_t pip_id) { + SOKOL_ASSERT(p); + if (SG_INVALID_ID != pip_id) { + _sg_pipeline_t* pip = _sg_pipeline_at(p, pip_id); + if (pip->slot.id == pip_id) { + return pip; + } + } + return 0; +} + +_SOKOL_PRIVATE _sg_attachments_t* _sg_lookup_attachments(const _sg_pools_t* p, uint32_t atts_id) { + SOKOL_ASSERT(p); + if (SG_INVALID_ID != atts_id) { + _sg_attachments_t* atts = _sg_attachments_at(p, atts_id); + if (atts->slot.id == atts_id) { + return atts; + } + } + return 0; +} + +_SOKOL_PRIVATE void _sg_discard_all_resources(_sg_pools_t* p) { + /* this is a bit dumb since it loops over all pool slots to + find the occupied slots, on the other hand it is only ever + executed at shutdown + NOTE: ONLY EXECUTE THIS AT SHUTDOWN + ...because the free queues will not be reset + and the resource slots not be cleared! + */ + for (int i = 1; i < p->buffer_pool.size; i++) { + sg_resource_state state = p->buffers[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_buffer(&p->buffers[i]); + } + } + for (int i = 1; i < p->image_pool.size; i++) { + sg_resource_state state = p->images[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_image(&p->images[i]); + } + } + for (int i = 1; i < p->sampler_pool.size; i++) { + sg_resource_state state = p->samplers[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_sampler(&p->samplers[i]); + } + } + for (int i = 1; i < p->shader_pool.size; i++) { + sg_resource_state state = p->shaders[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_shader(&p->shaders[i]); + } + } + for (int i = 1; i < p->pipeline_pool.size; i++) { + sg_resource_state state = p->pipelines[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_pipeline(&p->pipelines[i]); + } + } + for (int i = 1; i < p->attachments_pool.size; i++) { + sg_resource_state state = p->attachments[i].slot.state; + if ((state == SG_RESOURCESTATE_VALID) || (state == SG_RESOURCESTATE_FAILED)) { + _sg_discard_attachments(&p->attachments[i]); + } + } +} + +// ████████ ██████ █████ ██████ ██ ██ ███████ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ███████ ██ █████ █████ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ +// +// >>tracker +_SOKOL_PRIVATE void _sg_tracker_init(_sg_tracker_t* tracker, uint32_t num) { + SOKOL_ASSERT(tracker); + SOKOL_ASSERT(num > 0); + SOKOL_ASSERT(0 == tracker->size); + SOKOL_ASSERT(0 == tracker->cur); + SOKOL_ASSERT(0 == tracker->items); + tracker->size = (uint32_t)num; + tracker->items = (uint32_t*)_sg_malloc_clear(num * sizeof(uint32_t)); +} + +_SOKOL_PRIVATE void _sg_tracker_discard(_sg_tracker_t* tracker) { + SOKOL_ASSERT(tracker); + if (tracker->items) { + _sg_free(tracker->items); + } + tracker->size = 0; + tracker->cur = 0; + tracker->items = 0; +} + +_SOKOL_PRIVATE void _sg_tracker_reset(_sg_tracker_t* tracker) { + SOKOL_ASSERT(tracker && tracker->items); + tracker->cur = 0; +} + +_SOKOL_PRIVATE bool _sg_tracker_add(_sg_tracker_t* tracker, uint32_t res_id) { + SOKOL_ASSERT(tracker && tracker->items); + if (tracker->cur < tracker->size) { + tracker->items[tracker->cur++] = res_id; + return true; + } else { + return false; + } +} + +// ██ ██ █████ ██ ██ ██████ █████ ████████ ██ ██████ ███ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ███████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ████ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ██ ████ +// +// >>validation +#if defined(SOKOL_DEBUG) +_SOKOL_PRIVATE void _sg_validate_begin(void) { + _sg.validate_error = SG_LOGITEM_OK; +} + +_SOKOL_PRIVATE bool _sg_validate_end(void) { + if (_sg.validate_error != SG_LOGITEM_OK) { + #if !defined(SOKOL_VALIDATE_NON_FATAL) + _SG_PANIC(VALIDATION_FAILED); + return false; + #else + return false; + #endif + } else { + return true; + } +} +#endif + +_SOKOL_PRIVATE bool _sg_validate_buffer_desc(const sg_buffer_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_BUFFERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_BUFFERDESC_CANARY); + _SG_VALIDATE(desc->size > 0, VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE); + bool injected = (0 != desc->gl_buffers[0]) || + (0 != desc->mtl_buffers[0]) || + (0 != desc->d3d11_buffer) || + (0 != desc->wgpu_buffer); + if (!injected && (desc->usage == SG_USAGE_IMMUTABLE)) { + if (desc->data.ptr) { + _SG_VALIDATE(desc->size == desc->data.size, VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE); + } else { + _SG_VALIDATE(desc->data.size == 0, VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE); + } + } else { + _SG_VALIDATE(0 == desc->data.ptr, VALIDATE_BUFFERDESC_EXPECT_NO_DATA); + _SG_VALIDATE(desc->data.size == 0, VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE); + } + if (desc->type == SG_BUFFERTYPE_STORAGEBUFFER) { + _SG_VALIDATE(_sg.features.compute, VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED); + _SG_VALIDATE(_sg_multiple_u64(desc->size, 4), VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4); + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE void _sg_validate_image_data(const sg_image_data* data, sg_pixel_format fmt, int width, int height, int num_faces, int num_mips, int num_slices) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(data); + _SOKOL_UNUSED(fmt); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(num_faces); + _SOKOL_UNUSED(num_mips); + _SOKOL_UNUSED(num_slices); + #else + for (int face_index = 0; face_index < num_faces; face_index++) { + for (int mip_index = 0; mip_index < num_mips; mip_index++) { + const bool has_data = data->subimage[face_index][mip_index].ptr != 0; + const bool has_size = data->subimage[face_index][mip_index].size > 0; + _SG_VALIDATE(has_data && has_size, VALIDATE_IMAGEDATA_NODATA); + const int mip_width = _sg_miplevel_dim(width, mip_index); + const int mip_height = _sg_miplevel_dim(height, mip_index); + const int bytes_per_slice = _sg_surface_pitch(fmt, mip_width, mip_height, 1); + const int expected_size = bytes_per_slice * num_slices; + _SG_VALIDATE(expected_size == (int)data->subimage[face_index][mip_index].size, VALIDATE_IMAGEDATA_DATA_SIZE); + } + } + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_image_desc(const sg_image_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_IMAGEDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_IMAGEDESC_CANARY); + _SG_VALIDATE(desc->width > 0, VALIDATE_IMAGEDESC_WIDTH); + _SG_VALIDATE(desc->height > 0, VALIDATE_IMAGEDESC_HEIGHT); + const sg_pixel_format fmt = desc->pixel_format; + const sg_usage usage = desc->usage; + const bool injected = (0 != desc->gl_textures[0]) || + (0 != desc->mtl_textures[0]) || + (0 != desc->d3d11_texture) || + (0 != desc->wgpu_texture); + if (_sg_is_depth_or_depth_stencil_format(fmt)) { + _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE); + } + if (desc->render_target) { + SOKOL_ASSERT(((int)fmt >= 0) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + _SG_VALIDATE(_sg.formats[fmt].render, VALIDATE_IMAGEDESC_RT_PIXELFORMAT); + _SG_VALIDATE(usage == SG_USAGE_IMMUTABLE, VALIDATE_IMAGEDESC_RT_IMMUTABLE); + _SG_VALIDATE(desc->data.subimage[0][0].ptr==0, VALIDATE_IMAGEDESC_RT_NO_DATA); + if (desc->sample_count > 1) { + _SG_VALIDATE(_sg.formats[fmt].msaa, VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT); + _SG_VALIDATE(desc->num_mipmaps == 1, VALIDATE_IMAGEDESC_MSAA_NUM_MIPMAPS); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_3D, VALIDATE_IMAGEDESC_MSAA_3D_IMAGE); + _SG_VALIDATE(desc->type != SG_IMAGETYPE_CUBE, VALIDATE_IMAGEDESC_MSAA_CUBE_IMAGE); + } + } else { + _SG_VALIDATE(desc->sample_count == 1, VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); + const bool valid_nonrt_fmt = !_sg_is_valid_rendertarget_depth_format(fmt); + _SG_VALIDATE(valid_nonrt_fmt, VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT); + const bool is_compressed = _sg_is_compressed_pixel_format(desc->pixel_format); + const bool is_immutable = (usage == SG_USAGE_IMMUTABLE); + if (is_compressed) { + _SG_VALIDATE(is_immutable, VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE); + } + if (!injected && is_immutable) { + // image desc must have valid data + _sg_validate_image_data(&desc->data, + desc->pixel_format, + desc->width, + desc->height, + (desc->type == SG_IMAGETYPE_CUBE) ? 6 : 1, + desc->num_mipmaps, + desc->num_slices); + } else { + // image desc must not have data + for (int face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + const bool no_data = 0 == desc->data.subimage[face_index][mip_index].ptr; + const bool no_size = 0 == desc->data.subimage[face_index][mip_index].size; + if (injected) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_INJECTED_NO_DATA); + } + if (!is_immutable) { + _SG_VALIDATE(no_data && no_size, VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA); + } + } + } + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_sampler_desc(const sg_sampler_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SAMPLERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SAMPLERDESC_CANARY); + // restriction from WebGPU: when anisotropy > 1, all filters must be linear + if (desc->max_anisotropy > 1) { + _SG_VALIDATE((desc->min_filter == SG_FILTER_LINEAR) + && (desc->mag_filter == SG_FILTER_LINEAR) + && (desc->mipmap_filter == SG_FILTER_LINEAR), + VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING); + } + return _sg_validate_end(); + #endif +} + +typedef struct { + uint64_t lo, hi; +} _sg_u128_t; + +_SOKOL_PRIVATE _sg_u128_t _sg_u128(void) { + _sg_u128_t res; + _sg_clear(&res, sizeof(res)); + return res; +} + +_SOKOL_PRIVATE _sg_u128_t _sg_validate_set_slot_bit(_sg_u128_t bits, sg_shader_stage stage, uint8_t slot) { + switch (stage) { + case SG_SHADERSTAGE_NONE: + SOKOL_ASSERT(slot < 128); + if (slot < 64) { + bits.lo |= 1ULL << slot; + } else { + bits.hi |= 1ULL << (slot - 64); + } + break; + case SG_SHADERSTAGE_VERTEX: + SOKOL_ASSERT(slot < 64); + bits.lo |= 1ULL << slot; + break; + case SG_SHADERSTAGE_FRAGMENT: + SOKOL_ASSERT(slot < 64); + bits.hi |= 1ULL << slot; + break; + case SG_SHADERSTAGE_COMPUTE: + SOKOL_ASSERT(slot < 64); + bits.lo |= 1ULL << slot; + break; + default: + SOKOL_UNREACHABLE; + break; + } + return bits; +} + +_SOKOL_PRIVATE bool _sg_validate_slot_bits(_sg_u128_t bits, sg_shader_stage stage, uint8_t slot) { + _sg_u128_t mask = _sg_u128(); + switch (stage) { + case SG_SHADERSTAGE_NONE: + SOKOL_ASSERT(slot < 128); + if (slot < 64) { + mask.lo = 1ULL << slot; + } else { + mask.hi = 1ULL << (slot - 64); + } + break; + case SG_SHADERSTAGE_VERTEX: + SOKOL_ASSERT(slot < 64); + mask.lo = 1ULL << slot; + break; + case SG_SHADERSTAGE_FRAGMENT: + SOKOL_ASSERT(slot < 64); + mask.hi = 1ULL << slot; + break; + case SG_SHADERSTAGE_COMPUTE: + SOKOL_ASSERT(slot < 64); + mask.lo = 1ULL << slot; + break; + default: + SOKOL_UNREACHABLE; + break; + } + return ((bits.lo & mask.lo) == 0) && ((bits.hi & mask.hi) == 0); +} + +_SOKOL_PRIVATE bool _sg_validate_shader_desc(const sg_shader_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + bool is_compute_shader = (desc->compute_func.source != 0) || (desc->compute_func.bytecode.ptr != 0); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SHADERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SHADERDESC_CANARY); + #if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3) || defined(SOKOL_WGPU) + // on GL or WebGPU, must provide shader source code + if (is_compute_shader) { + _SG_VALIDATE(0 != desc->compute_func.source, VALIDATE_SHADERDESC_COMPUTE_SOURCE); + } else { + _SG_VALIDATE(0 != desc->vertex_func.source, VALIDATE_SHADERDESC_VERTEX_SOURCE); + _SG_VALIDATE(0 != desc->fragment_func.source, VALIDATE_SHADERDESC_FRAGMENT_SOURCE); + } + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + // on Metal or D3D11, must provide shader source code or byte code + if (is_compute_shader) { + _SG_VALIDATE((0 != desc->compute_func.source) || (0 != desc->compute_func.bytecode.ptr), VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE); + } else { + _SG_VALIDATE((0 != desc->vertex_func.source)|| (0 != desc->vertex_func.bytecode.ptr), VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE); + _SG_VALIDATE((0 != desc->fragment_func.source) || (0 != desc->fragment_func.bytecode.ptr), VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE); + } + #else + // Dummy Backend, don't require source or bytecode + #endif + if (is_compute_shader) { + _SG_VALIDATE((0 == desc->vertex_func.source) && (0 == desc->vertex_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO); + _SG_VALIDATE((0 == desc->fragment_func.source) && (0 == desc->fragment_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO); + } else { + _SG_VALIDATE((0 == desc->compute_func.source) && (0 == desc->compute_func.bytecode.ptr), VALIDATE_SHADERDESC_INVALID_SHADER_COMBO); + } + #if defined(SOKOL_METAL) + if (is_compute_shader) { + _SG_VALIDATE(desc->mtl_threads_per_threadgroup.x > 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP); + _SG_VALIDATE(desc->mtl_threads_per_threadgroup.y > 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP); + _SG_VALIDATE(desc->mtl_threads_per_threadgroup.z > 0, VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP); + } + #endif + for (size_t i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + if (desc->attrs[i].glsl_name) { + _SG_VALIDATE(strlen(desc->attrs[i].glsl_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + if (desc->attrs[i].hlsl_sem_name) { + _SG_VALIDATE(strlen(desc->attrs[i].hlsl_sem_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + } + // if shader byte code, the size must also be provided + if (0 != desc->vertex_func.bytecode.ptr) { + _SG_VALIDATE(desc->vertex_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + if (0 != desc->fragment_func.bytecode.ptr) { + _SG_VALIDATE(desc->fragment_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + if (0 != desc->compute_func.bytecode.ptr) { + _SG_VALIDATE(desc->compute_func.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + + #if defined(SOKOL_METAL) + _sg_u128_t msl_buf_bits = _sg_u128(); + _sg_u128_t msl_tex_bits = _sg_u128(); + _sg_u128_t msl_smp_bits = _sg_u128(); + #elif defined(SOKOL_D3D11) + _sg_u128_t hlsl_buf_bits = _sg_u128(); + _sg_u128_t hlsl_srv_bits = _sg_u128(); + _sg_u128_t hlsl_uav_bits = _sg_u128(); + _sg_u128_t hlsl_smp_bits = _sg_u128(); + #elif defined(_SOKOL_ANY_GL) + _sg_u128_t glsl_bnd_bits = _sg_u128(); + #elif defined(SOKOL_WGPU) + _sg_u128_t wgsl_group0_bits = _sg_u128(); + _sg_u128_t wgsl_group1_bits = _sg_u128(); + #endif + for (size_t ub_idx = 0; ub_idx < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_idx++) { + const sg_shader_uniform_block* ub_desc = &desc->uniform_blocks[ub_idx]; + if (ub_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + _SG_VALIDATE(ub_desc->size > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO); + #if defined(SOKOL_METAL) + _SG_VALIDATE(ub_desc->msl_buffer_n < _SG_MTL_MAX_STAGE_UB_BINDINGS, VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, ub_desc->stage, ub_desc->msl_buffer_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION); + msl_buf_bits = _sg_validate_set_slot_bit(msl_buf_bits, ub_desc->stage, ub_desc->msl_buffer_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(ub_desc->hlsl_register_b_n < _SG_D3D11_MAX_STAGE_UB_BINDINGS, VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_buf_bits, ub_desc->stage, ub_desc->hlsl_register_b_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION); + hlsl_buf_bits = _sg_validate_set_slot_bit(hlsl_buf_bits, ub_desc->stage, ub_desc->hlsl_register_b_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(ub_desc->wgsl_group0_binding_n < _SG_WGPU_MAX_UB_BINDGROUP_BIND_SLOTS, VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group0_bits, SG_SHADERSTAGE_NONE, ub_desc->wgsl_group0_binding_n), VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION); + wgsl_group0_bits = _sg_validate_set_slot_bit(wgsl_group0_bits, SG_SHADERSTAGE_NONE, ub_desc->wgsl_group0_binding_n); + #endif + #if defined(_SOKOL_ANY_GL) + bool uniforms_continuous = true; + uint32_t uniform_offset = 0; + int num_uniforms = 0; + for (size_t u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) { + const sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index]; + if (u_desc->type != SG_UNIFORMTYPE_INVALID) { + _SG_VALIDATE(uniforms_continuous, VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS); + _SG_VALIDATE(u_desc->glsl_name, VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME); + const int array_count = u_desc->array_count; + _SG_VALIDATE(array_count > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT); + const uint32_t u_align = _sg_uniform_alignment(u_desc->type, array_count, ub_desc->layout); + const uint32_t u_size = _sg_uniform_size(u_desc->type, array_count, ub_desc->layout); + uniform_offset = _sg_align_u32(uniform_offset, u_align); + uniform_offset += u_size; + num_uniforms++; + // with std140, arrays are only allowed for FLOAT4, INT4, MAT4 + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + if (array_count > 1) { + _SG_VALIDATE((u_desc->type == SG_UNIFORMTYPE_FLOAT4) || (u_desc->type == SG_UNIFORMTYPE_INT4) || (u_desc->type == SG_UNIFORMTYPE_MAT4), VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE); + } + } + } else { + uniforms_continuous = false; + } + } + if (ub_desc->layout == SG_UNIFORMLAYOUT_STD140) { + uniform_offset = _sg_align_u32(uniform_offset, 16); + } + _SG_VALIDATE((size_t)uniform_offset == ub_desc->size, VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH); + _SG_VALIDATE(num_uniforms > 0, VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS); + #endif + } + + for (size_t sbuf_idx = 0; sbuf_idx < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_idx++) { + const sg_shader_storage_buffer* sbuf_desc = &desc->storage_buffers[sbuf_idx]; + if (sbuf_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + #if defined(SOKOL_METAL) + _SG_VALIDATE((sbuf_desc->msl_buffer_n >= _SG_MTL_MAX_STAGE_UB_BINDINGS) && (sbuf_desc->msl_buffer_n < _SG_MTL_MAX_STAGE_UB_SBUF_BINDINGS), VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n), VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION); + msl_buf_bits = _sg_validate_set_slot_bit(msl_buf_bits, sbuf_desc->stage, sbuf_desc->msl_buffer_n); + #elif defined(SOKOL_D3D11) + if (sbuf_desc->readonly) { + _SG_VALIDATE(sbuf_desc->hlsl_register_t_n < _SG_D3D11_MAX_STAGE_SRV_BINDINGS, VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION); + hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_t_n); + } else { + _SG_VALIDATE(sbuf_desc->hlsl_register_u_n < _SG_D3D11_MAX_STAGE_UAV_BINDINGS, VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n), VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION); + hlsl_uav_bits = _sg_validate_set_slot_bit(hlsl_uav_bits, sbuf_desc->stage, sbuf_desc->hlsl_register_u_n); + } + #elif defined(_SOKOL_ANY_GL) + _SG_VALIDATE(sbuf_desc->glsl_binding_n < _SG_GL_MAX_SBUF_BINDINGS, VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(glsl_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n), VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_COLLISION); + glsl_bnd_bits = _sg_validate_set_slot_bit(glsl_bnd_bits, SG_SHADERSTAGE_NONE, sbuf_desc->glsl_binding_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(sbuf_desc->wgsl_group1_binding_n < _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS, VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, sbuf_desc->wgsl_group1_binding_n); + #endif + } + + uint32_t img_slot_mask = 0; + for (size_t img_idx = 0; img_idx < SG_MAX_IMAGE_BINDSLOTS; img_idx++) { + const sg_shader_image* img_desc = &desc->images[img_idx]; + if (img_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + img_slot_mask |= (1 << img_idx); + #if defined(SOKOL_METAL) + _SG_VALIDATE(img_desc->msl_texture_n < _SG_MTL_MAX_STAGE_IMAGE_BINDINGS, VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_tex_bits, img_desc->stage, img_desc->msl_texture_n), VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_COLLISION); + msl_tex_bits = _sg_validate_set_slot_bit(msl_tex_bits, img_desc->stage, img_desc->msl_texture_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(img_desc->hlsl_register_t_n < _SG_D3D11_MAX_STAGE_SRV_BINDINGS, VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_srv_bits, img_desc->stage, img_desc->hlsl_register_t_n), VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_COLLISION); + hlsl_srv_bits = _sg_validate_set_slot_bit(hlsl_srv_bits, img_desc->stage, img_desc->hlsl_register_t_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(img_desc->wgsl_group1_binding_n < _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS, VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, img_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, img_desc->wgsl_group1_binding_n); + #endif + } + + uint32_t smp_slot_mask = 0; + for (size_t smp_idx = 0; smp_idx < SG_MAX_SAMPLER_BINDSLOTS; smp_idx++) { + const sg_shader_sampler* smp_desc = &desc->samplers[smp_idx]; + if (smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + smp_slot_mask |= (1 << smp_idx); + #if defined(SOKOL_METAL) + _SG_VALIDATE(smp_desc->msl_sampler_n < _SG_MTL_MAX_STAGE_SAMPLER_BINDINGS, VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(msl_smp_bits, smp_desc->stage, smp_desc->msl_sampler_n), VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION); + msl_smp_bits = _sg_validate_set_slot_bit(msl_smp_bits, smp_desc->stage, smp_desc->msl_sampler_n); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(smp_desc->hlsl_register_s_n < _SG_D3D11_MAX_STAGE_SMP_BINDINGS, VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(hlsl_smp_bits, smp_desc->stage, smp_desc->hlsl_register_s_n), VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION); + hlsl_smp_bits = _sg_validate_set_slot_bit(hlsl_smp_bits, smp_desc->stage, smp_desc->hlsl_register_s_n); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(smp_desc->wgsl_group1_binding_n < _SG_WGPU_MAX_IMG_SMP_SBUF_BIND_SLOTS, VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE); + _SG_VALIDATE(_sg_validate_slot_bits(wgsl_group1_bits, SG_SHADERSTAGE_NONE, smp_desc->wgsl_group1_binding_n), VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION); + wgsl_group1_bits = _sg_validate_set_slot_bit(wgsl_group1_bits, SG_SHADERSTAGE_NONE, smp_desc->wgsl_group1_binding_n); + #endif + } + + uint32_t ref_img_slot_mask = 0; + uint32_t ref_smp_slot_mask = 0; + for (size_t img_smp_idx = 0; img_smp_idx < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_idx++) { + const sg_shader_image_sampler_pair* img_smp_desc = &desc->image_sampler_pairs[img_smp_idx]; + if (img_smp_desc->stage == SG_SHADERSTAGE_NONE) { + continue; + } + #if defined(_SOKOL_ANY_GL) + _SG_VALIDATE(img_smp_desc->glsl_name != 0, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_GLSL_NAME); + #endif + const bool img_slot_in_range = img_smp_desc->image_slot < SG_MAX_IMAGE_BINDSLOTS; + const bool smp_slot_in_range = img_smp_desc->sampler_slot < SG_MAX_SAMPLER_BINDSLOTS; + _SG_VALIDATE(img_slot_in_range, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(smp_slot_in_range, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE); + if (img_slot_in_range && smp_slot_in_range) { + ref_img_slot_mask |= 1 << img_smp_desc->image_slot; + ref_smp_slot_mask |= 1 << img_smp_desc->sampler_slot; + const sg_shader_image* img_desc = &desc->images[img_smp_desc->image_slot]; + const sg_shader_sampler* smp_desc = &desc->samplers[img_smp_desc->sampler_slot]; + _SG_VALIDATE(img_desc->stage == img_smp_desc->stage, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_STAGE_MISMATCH); + _SG_VALIDATE(smp_desc->stage == img_smp_desc->stage, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH); + const bool needs_nonfiltering = (img_desc->sample_type == SG_IMAGESAMPLETYPE_UINT) + || (img_desc->sample_type == SG_IMAGESAMPLETYPE_SINT) + || (img_desc->sample_type == SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT); + const bool needs_comparison = img_desc->sample_type == SG_IMAGESAMPLETYPE_DEPTH; + if (needs_nonfiltering) { + _SG_VALIDATE(needs_nonfiltering && (smp_desc->sampler_type == SG_SAMPLERTYPE_NONFILTERING), VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED); + } + if (needs_comparison) { + _SG_VALIDATE(needs_comparison && (smp_desc->sampler_type == SG_SAMPLERTYPE_COMPARISON), VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED); + } + } + } + // each image and sampler must be referenced by an image sampler + _SG_VALIDATE(img_slot_mask == ref_img_slot_mask, VALIDATE_SHADERDESC_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS); + _SG_VALIDATE(smp_slot_mask == ref_smp_slot_mask, VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS); + + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_pipeline_desc(const sg_pipeline_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_PIPELINEDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_PIPELINEDESC_CANARY); + _SG_VALIDATE(desc->shader.id != SG_INVALID_ID, VALIDATE_PIPELINEDESC_SHADER); + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); + _SG_VALIDATE(0 != shd, VALIDATE_PIPELINEDESC_SHADER); + if (shd) { + _SG_VALIDATE(shd->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_PIPELINEDESC_SHADER); + if (desc->compute) { + _SG_VALIDATE(shd->cmn.is_compute, VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED); + } else { + _SG_VALIDATE(!shd->cmn.is_compute, VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED); + bool attrs_cont = true; + for (size_t attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + const sg_vertex_attr_state* a_state = &desc->layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + attrs_cont = false; + continue; + } + _SG_VALIDATE(attrs_cont, VALIDATE_PIPELINEDESC_NO_CONT_ATTRS); + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + // vertex format must match expected shader attribute base type (if provided) + if (shd->cmn.attrs[attr_index].base_type != SG_SHADERATTRBASETYPE_UNDEFINED) { + if (_sg_vertexformat_basetype(a_state->format) != shd->cmn.attrs[attr_index].base_type) { + _SG_VALIDATE(false, VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "attr format:"); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, _sg_vertexformat_to_string(a_state->format)); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, "shader attr base type:"); + _SG_LOGMSG(VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH, _sg_shaderattrbasetype_to_string(shd->cmn.attrs[attr_index].base_type)); + } + } + #if defined(SOKOL_D3D11) + // on D3D11, semantic names (and semantic indices) must be provided + _SG_VALIDATE(!_sg_strempty(&shd->d3d11.attrs[attr_index].sem_name), VALIDATE_PIPELINEDESC_ATTR_SEMANTICS); + #endif + } + // must only use readonly storage buffer bindings in render pipelines + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(shd->cmn.storage_buffers[i].readonly, VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS); + } + } + for (int buf_index = 0; buf_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; buf_index++) { + const sg_vertex_buffer_layout_state* l_state = &desc->layout.buffers[buf_index]; + if (l_state->stride == 0) { + continue; + } + _SG_VALIDATE(_sg_multiple_u64((uint64_t)l_state->stride, 4), VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4); + } + } + } + for (size_t color_index = 0; color_index < (size_t)desc->color_count; color_index++) { + const sg_blend_state* bs = &desc->colors[color_index].blend; + if ((bs->op_rgb == SG_BLENDOP_MIN) || (bs->op_rgb == SG_BLENDOP_MAX)) { + _SG_VALIDATE((bs->src_factor_rgb == SG_BLENDFACTOR_ONE) && (bs->dst_factor_rgb == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE); + } + if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) { + _SG_VALIDATE((bs->src_factor_alpha == SG_BLENDFACTOR_ONE) && (bs->dst_factor_alpha == SG_BLENDFACTOR_ONE), VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE); + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_attachments_desc(const sg_attachments_desc* desc) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(desc); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(desc); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_ATTACHMENTSDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_ATTACHMENTSDESC_CANARY); + bool atts_cont = true; + int color_width = -1, color_height = -1, color_sample_count = -1; + bool has_color_atts = false; + for (int att_index = 0; att_index < SG_MAX_COLOR_ATTACHMENTS; att_index++) { + const sg_attachment_desc* att = &desc->colors[att_index]; + if (att->image.id == SG_INVALID_ID) { + atts_cont = false; + continue; + } + _SG_VALIDATE(atts_cont, VALIDATE_ATTACHMENTSDESC_NO_CONT_COLOR_ATTS); + has_color_atts = true; + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); + _SG_VALIDATE(img, VALIDATE_ATTACHMENTSDESC_IMAGE); + if (0 != img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_IMAGE); + _SG_VALIDATE(img->cmn.render_target, VALIDATE_ATTACHMENTSDESC_IMAGE_NO_RT); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(att->slice < 6, VALIDATE_ATTACHMENTSDESC_FACE); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_LAYER); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_SLICE); + } + if (att_index == 0) { + color_width = _sg_miplevel_dim(img->cmn.width, att->mip_level); + color_height = _sg_miplevel_dim(img->cmn.height, att->mip_level); + color_sample_count = img->cmn.sample_count; + } else { + _SG_VALIDATE(color_width == _sg_miplevel_dim(img->cmn.width, att->mip_level), VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES); + _SG_VALIDATE(color_height == _sg_miplevel_dim(img->cmn.height, att->mip_level), VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES); + _SG_VALIDATE(color_sample_count == img->cmn.sample_count, VALIDATE_ATTACHMENTSDESC_IMAGE_SAMPLE_COUNTS); + } + _SG_VALIDATE(_sg_is_valid_rendertarget_color_format(img->cmn.pixel_format), VALIDATE_ATTACHMENTSDESC_COLOR_INV_PIXELFORMAT); + + // check resolve attachment + const sg_attachment_desc* res_att = &desc->resolves[att_index]; + if (res_att->image.id != SG_INVALID_ID) { + // associated color attachment must be MSAA + _SG_VALIDATE(img->cmn.sample_count > 1, VALIDATE_ATTACHMENTSDESC_RESOLVE_COLOR_IMAGE_MSAA); + const _sg_image_t* res_img = _sg_lookup_image(&_sg.pools, res_att->image.id); + _SG_VALIDATE(res_img, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE); + if (res_img != 0) { + _SG_VALIDATE(res_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE); + _SG_VALIDATE(res_img->cmn.render_target, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_NO_RT); + _SG_VALIDATE(res_img->cmn.sample_count == 1, VALIDATE_ATTACHMENTSDESC_RESOLVE_SAMPLE_COUNT); + _SG_VALIDATE(res_att->mip_level < res_img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_RESOLVE_MIPLEVEL); + if (res_img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(res_att->slice < 6, VALIDATE_ATTACHMENTSDESC_RESOLVE_FACE); + } else if (res_img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(res_att->slice < res_img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_RESOLVE_LAYER); + } else if (res_img->cmn.type == SG_IMAGETYPE_3D) { + _SG_VALIDATE(res_att->slice < res_img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_RESOLVE_SLICE); + } + _SG_VALIDATE(img->cmn.pixel_format == res_img->cmn.pixel_format, VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_FORMAT); + _SG_VALIDATE(color_width == _sg_miplevel_dim(res_img->cmn.width, res_att->mip_level), VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + _SG_VALIDATE(color_height == _sg_miplevel_dim(res_img->cmn.height, res_att->mip_level), VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + } + } + } + } + bool has_depth_stencil_att = false; + if (desc->depth_stencil.image.id != SG_INVALID_ID) { + const sg_attachment_desc* att = &desc->depth_stencil; + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, att->image.id); + _SG_VALIDATE(img, VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE); + has_depth_stencil_att = true; + if (img) { + _SG_VALIDATE(img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE); + _SG_VALIDATE(att->mip_level < img->cmn.num_mipmaps, VALIDATE_ATTACHMENTSDESC_DEPTH_MIPLEVEL); + if (img->cmn.type == SG_IMAGETYPE_CUBE) { + _SG_VALIDATE(att->slice < 6, VALIDATE_ATTACHMENTSDESC_DEPTH_FACE); + } else if (img->cmn.type == SG_IMAGETYPE_ARRAY) { + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_DEPTH_LAYER); + } else if (img->cmn.type == SG_IMAGETYPE_3D) { + // NOTE: this can't actually happen because of VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE + _SG_VALIDATE(att->slice < img->cmn.num_slices, VALIDATE_ATTACHMENTSDESC_DEPTH_SLICE); + } + _SG_VALIDATE(img->cmn.render_target, VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_NO_RT); + _SG_VALIDATE((color_width == -1) || (color_width == _sg_miplevel_dim(img->cmn.width, att->mip_level)), VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + _SG_VALIDATE((color_height == -1) || (color_height == _sg_miplevel_dim(img->cmn.height, att->mip_level)), VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + _SG_VALIDATE((color_sample_count == -1) || (color_sample_count == img->cmn.sample_count), VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SAMPLE_COUNT); + _SG_VALIDATE(_sg_is_valid_rendertarget_depth_format(img->cmn.pixel_format), VALIDATE_ATTACHMENTSDESC_DEPTH_INV_PIXELFORMAT); + } + } + _SG_VALIDATE(has_color_atts || has_depth_stencil_att, VALIDATE_ATTACHMENTSDESC_NO_ATTACHMENTS); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_begin_pass(const sg_pass* pass) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(pass); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + const bool is_compute_pass = pass->compute; + const bool is_swapchain_pass = !is_compute_pass && (pass->attachments.id == SG_INVALID_ID); + const bool is_offscreen_pass = !(is_compute_pass || is_swapchain_pass); + _sg_validate_begin(); + _SG_VALIDATE(pass->_start_canary == 0, VALIDATE_BEGINPASS_CANARY); + _SG_VALIDATE(pass->_end_canary == 0, VALIDATE_BEGINPASS_CANARY); + if (is_compute_pass) { + // this is a compute pass + _SG_VALIDATE(pass->attachments.id == SG_INVALID_ID, VALIDATE_BEGINPASS_EXPECT_NO_ATTACHMENTS); + } else if (is_swapchain_pass) { + // this is a swapchain pass + _SG_VALIDATE(pass->swapchain.width > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH); + _SG_VALIDATE(pass->swapchain.height > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT); + _SG_VALIDATE(pass->swapchain.sample_count > 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT); + _SG_VALIDATE(pass->swapchain.color_format > SG_PIXELFORMAT_NONE, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT); + // NOTE: depth buffer is optional, so depth_format is allowed to be invalid + // NOTE: the GL framebuffer handle may actually be 0 + #if defined(SOKOL_METAL) + _SG_VALIDATE(pass->swapchain.metal.current_drawable != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE); + if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) { + _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET); + } else { + _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE); + } + if (pass->swapchain.sample_count > 1) { + _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture != 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE); + } else { + _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET); + } + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(pass->swapchain.d3d11.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW); + if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) { + _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET); + } else { + _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW); + } + if (pass->swapchain.sample_count > 1) { + _SG_VALIDATE(pass->swapchain.d3d11.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW); + } else { + _SG_VALIDATE(pass->swapchain.d3d11.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET); + } + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(pass->swapchain.wgpu.render_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW); + if (pass->swapchain.depth_format == SG_PIXELFORMAT_NONE) { + _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET); + } else { + _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW); + } + if (pass->swapchain.sample_count > 1) { + _SG_VALIDATE(pass->swapchain.wgpu.resolve_view != 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW); + } else { + _SG_VALIDATE(pass->swapchain.wgpu.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET); + } + #endif + } else { + // this is an 'offscreen pass' + const _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, pass->attachments.id); + if (atts) { + _SG_VALIDATE(atts->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_ATTACHMENTS_VALID); + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + const _sg_attachment_common_t* color_att = &atts->cmn.colors[i]; + const _sg_image_t* color_img = _sg_attachments_color_image(atts, i); + if (color_img) { + _SG_VALIDATE(color_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE); + _SG_VALIDATE(color_img->slot.id == color_att->image_id.id, VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE); + } + const _sg_attachment_common_t* resolve_att = &atts->cmn.resolves[i]; + const _sg_image_t* resolve_img = _sg_attachments_resolve_image(atts, i); + if (resolve_img) { + _SG_VALIDATE(resolve_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE); + _SG_VALIDATE(resolve_img->slot.id == resolve_att->image_id.id, VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE); + } + } + const _sg_image_t* ds_img = _sg_attachments_ds_image(atts); + if (ds_img) { + const _sg_attachment_common_t* att = &atts->cmn.depth_stencil; + _SG_VALIDATE(ds_img->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE); + _SG_VALIDATE(ds_img->slot.id == att->image_id.id, VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE); + } + } else { + _SG_VALIDATE(atts != 0, VALIDATE_BEGINPASS_ATTACHMENTS_EXISTS); + } + } + if (is_compute_pass || is_offscreen_pass) { + _SG_VALIDATE(pass->swapchain.width == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET); + _SG_VALIDATE(pass->swapchain.height == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET); + _SG_VALIDATE(pass->swapchain.sample_count == 0, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET); + _SG_VALIDATE(pass->swapchain.color_format == _SG_PIXELFORMAT_DEFAULT, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET); + _SG_VALIDATE(pass->swapchain.depth_format == _SG_PIXELFORMAT_DEFAULT, VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET); + #if defined(SOKOL_METAL) + _SG_VALIDATE(pass->swapchain.metal.current_drawable == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET); + _SG_VALIDATE(pass->swapchain.metal.depth_stencil_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET); + _SG_VALIDATE(pass->swapchain.metal.msaa_color_texture == 0, VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET); + #elif defined(SOKOL_D3D11) + _SG_VALIDATE(pass->swapchain.d3d11.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.d3d11.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.d3d11.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET); + #elif defined(SOKOL_WGPU) + _SG_VALIDATE(pass->swapchain.wgpu.render_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.wgpu.depth_stencil_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET); + _SG_VALIDATE(pass->swapchain.wgpu.resolve_view == 0, VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET); + #elif defined(_SOKOL_ANY_GL) + _SG_VALIDATE(pass->swapchain.gl.framebuffer == 0, VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET); + #endif + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_viewport(int x, int y, int width, int height, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(origin_top_left); + #if !defined(SOKOL_DEBUG) + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_AVP_RENDERPASS_EXPECTED); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) { + _SOKOL_UNUSED(x); + _SOKOL_UNUSED(y); + _SOKOL_UNUSED(width); + _SOKOL_UNUSED(height); + _SOKOL_UNUSED(origin_top_left); + #if !defined(SOKOL_DEBUG) + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_ASR_RENDERPASS_EXPECTED); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_pipeline(sg_pipeline pip_id) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(pip_id); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + // the pipeline object must be alive and valid + _SG_VALIDATE(pip_id.id != SG_INVALID_ID, VALIDATE_APIP_PIPELINE_VALID_ID); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + _SG_VALIDATE(pip != 0, VALIDATE_APIP_PIPELINE_EXISTS); + if (!pip) { + return _sg_validate_end(); + } + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_PIPELINE_VALID); + // the pipeline's shader must be alive and valid + SOKOL_ASSERT(pip->shader); + _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_APIP_PASS_EXPECTED); + _SG_VALIDATE(pip->shader->slot.id == pip->cmn.shader_id.id, VALIDATE_APIP_SHADER_EXISTS); + _SG_VALIDATE(pip->shader->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_SHADER_VALID); + if (pip->cmn.is_compute) { + _SG_VALIDATE(_sg.cur_pass.is_compute, VALIDATE_APIP_COMPUTEPASS_EXPECTED); + } else { + _SG_VALIDATE(!_sg.cur_pass.is_compute, VALIDATE_APIP_RENDERPASS_EXPECTED); + // check that pipeline attributes match current pass attributes + if (_sg.cur_pass.atts_id.id != SG_INVALID_ID) { + // an offscreen pass + const _sg_attachments_t* atts = _sg.cur_pass.atts; + SOKOL_ASSERT(atts); + _SG_VALIDATE(atts->slot.id == _sg.cur_pass.atts_id.id, VALIDATE_APIP_CURPASS_ATTACHMENTS_EXISTS); + _SG_VALIDATE(atts->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_APIP_CURPASS_ATTACHMENTS_VALID); + + _SG_VALIDATE(pip->cmn.color_count == atts->cmn.num_colors, VALIDATE_APIP_ATT_COUNT); + for (int i = 0; i < pip->cmn.color_count; i++) { + const _sg_image_t* att_img = _sg_attachments_color_image(atts, i); + _SG_VALIDATE(pip->cmn.colors[i].pixel_format == att_img->cmn.pixel_format, VALIDATE_APIP_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == att_img->cmn.sample_count, VALIDATE_APIP_SAMPLE_COUNT); + } + const _sg_image_t* att_dsimg = _sg_attachments_ds_image(atts); + if (att_dsimg) { + _SG_VALIDATE(pip->cmn.depth.pixel_format == att_dsimg->cmn.pixel_format, VALIDATE_APIP_DEPTH_FORMAT); + } else { + _SG_VALIDATE(pip->cmn.depth.pixel_format == SG_PIXELFORMAT_NONE, VALIDATE_APIP_DEPTH_FORMAT); + } + } else { + // default pass + _SG_VALIDATE(pip->cmn.color_count == 1, VALIDATE_APIP_ATT_COUNT); + _SG_VALIDATE(pip->cmn.colors[0].pixel_format == _sg.cur_pass.swapchain.color_fmt, VALIDATE_APIP_COLOR_FORMAT); + _SG_VALIDATE(pip->cmn.depth.pixel_format == _sg.cur_pass.swapchain.depth_fmt, VALIDATE_APIP_DEPTH_FORMAT); + _SG_VALIDATE(pip->cmn.sample_count == _sg.cur_pass.swapchain.sample_count, VALIDATE_APIP_SAMPLE_COUNT); + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(bindings); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + + // must be called in a pass + _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_ABND_PASS_EXPECTED); + + // bindings must not be empty + bool has_any_bindings = bindings->index_buffer.id != SG_INVALID_ID; + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + has_any_bindings |= bindings->vertex_buffers[i].id != SG_INVALID_ID; + } + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + has_any_bindings |= bindings->images[i].id != SG_INVALID_ID; + } + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + has_any_bindings |= bindings->samplers[i].id != SG_INVALID_ID; + } + if (!has_any_bindings) for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + has_any_bindings |= bindings->storage_buffers[i].id != SG_INVALID_ID; + } + _SG_VALIDATE(has_any_bindings, VALIDATE_ABND_EMPTY_BINDINGS); + + // a pipeline object must have been applied + _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_ABND_PIPELINE); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); + _SG_VALIDATE(pip != 0, VALIDATE_ABND_PIPELINE_EXISTS); + if (!pip) { + return _sg_validate_end(); + } + _SG_VALIDATE(pip->slot.state == SG_RESOURCESTATE_VALID, VALIDATE_ABND_PIPELINE_VALID); + SOKOL_ASSERT(pip->shader && (pip->cmn.shader_id.id == pip->shader->slot.id)); + const _sg_shader_t* shd = pip->shader; + + if (_sg.cur_pass.is_compute) { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + _SG_VALIDATE(bindings->vertex_buffers[i].id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBS); + } + } else { + // has expected vertex buffers, and vertex buffers still exist + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + if (pip->cmn.vertex_buffer_layout_active[i]) { + _SG_VALIDATE(bindings->vertex_buffers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_VB); + // buffers in vertex-buffer-slots must be of type SG_BUFFERTYPE_VERTEXBUFFER + if (bindings->vertex_buffers[i].id != SG_INVALID_ID) { + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->vertex_buffers[i].id); + _SG_VALIDATE(buf != 0, VALIDATE_ABND_VB_EXISTS); + if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(SG_BUFFERTYPE_VERTEXBUFFER == buf->cmn.type, VALIDATE_ABND_VB_TYPE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_VB_OVERFLOW); + } + } + } + } + } + + if (_sg.cur_pass.is_compute) { + _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_COMPUTE_EXPECTED_NO_IB); + } else { + // index buffer expected or not, and index buffer still exists + if (pip->cmn.index_type == SG_INDEXTYPE_NONE) { + // pipeline defines non-indexed rendering, but index buffer provided + _SG_VALIDATE(bindings->index_buffer.id == SG_INVALID_ID, VALIDATE_ABND_IB); + } else { + // pipeline defines indexed rendering, but no index buffer provided + _SG_VALIDATE(bindings->index_buffer.id != SG_INVALID_ID, VALIDATE_ABND_NO_IB); + } + if (bindings->index_buffer.id != SG_INVALID_ID) { + // buffer in index-buffer-slot must be of type SG_BUFFERTYPE_INDEXBUFFER + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, bindings->index_buffer.id); + _SG_VALIDATE(buf != 0, VALIDATE_ABND_IB_EXISTS); + if (buf && buf->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(SG_BUFFERTYPE_INDEXBUFFER == buf->cmn.type, VALIDATE_ABND_IB_TYPE); + _SG_VALIDATE(!buf->cmn.append_overflow, VALIDATE_ABND_IB_OVERFLOW); + } + } + } + + // has expected images + for (size_t i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(bindings->images[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_IMAGE_BINDING); + if (bindings->images[i].id != SG_INVALID_ID) { + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->images[i].id); + _SG_VALIDATE(img != 0, VALIDATE_ABND_IMG_EXISTS); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(img->cmn.type == shd->cmn.images[i].image_type, VALIDATE_ABND_IMAGE_TYPE_MISMATCH); + if (!_sg.features.msaa_image_bindings) { + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_ABND_IMAGE_MSAA); + } + if (shd->cmn.images[i].multisampled) { + _SG_VALIDATE(img->cmn.sample_count > 1, VALIDATE_ABND_EXPECTED_MULTISAMPLED_IMAGE); + } + const _sg_pixelformat_info_t* info = &_sg.formats[img->cmn.pixel_format]; + switch (shd->cmn.images[i].sample_type) { + case SG_IMAGESAMPLETYPE_FLOAT: + _SG_VALIDATE(info->filter, VALIDATE_ABND_EXPECTED_FILTERABLE_IMAGE); + break; + case SG_IMAGESAMPLETYPE_DEPTH: + _SG_VALIDATE(info->depth, VALIDATE_ABND_EXPECTED_DEPTH_IMAGE); + break; + default: + break; + } + } + } + } + } + + // has expected samplers + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(bindings->samplers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_SAMPLER_BINDING); + if (bindings->samplers[i].id != SG_INVALID_ID) { + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, bindings->samplers[i].id); + _SG_VALIDATE(smp != 0, VALIDATE_ABND_SMP_EXISTS); + if (smp) { + if (shd->cmn.samplers[i].sampler_type == SG_SAMPLERTYPE_COMPARISON) { + _SG_VALIDATE(smp->cmn.compare != SG_COMPAREFUNC_NEVER, VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER); + } else { + _SG_VALIDATE(smp->cmn.compare == SG_COMPAREFUNC_NEVER, VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER); + } + if (shd->cmn.samplers[i].sampler_type == SG_SAMPLERTYPE_NONFILTERING) { + const bool nonfiltering = (smp->cmn.min_filter != SG_FILTER_LINEAR) + && (smp->cmn.mag_filter != SG_FILTER_LINEAR) + && (smp->cmn.mipmap_filter != SG_FILTER_LINEAR); + _SG_VALIDATE(nonfiltering, VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER); + } + } + } + } + } + + // has expected storage buffers + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE) { + _SG_VALIDATE(bindings->storage_buffers[i].id != SG_INVALID_ID, VALIDATE_ABND_EXPECTED_STORAGEBUFFER_BINDING); + if (bindings->storage_buffers[i].id != SG_INVALID_ID) { + const _sg_buffer_t* sbuf = _sg_lookup_buffer(&_sg.pools, bindings->storage_buffers[i].id); + _SG_VALIDATE(sbuf != 0, VALIDATE_ABND_STORAGEBUFFER_EXISTS); + if (sbuf) { + _SG_VALIDATE(sbuf->cmn.type == SG_BUFFERTYPE_STORAGEBUFFER, VALIDATE_ABND_STORAGEBUFFER_BINDING_BUFFERTYPE); + // read/write bindings are only allowed for immutable buffers + if (!shd->cmn.storage_buffers[i].readonly) { + _SG_VALIDATE(sbuf->cmn.usage == SG_USAGE_IMMUTABLE, VALIDATE_ABND_STORAGEBUFFER_READWRITE_IMMUTABLE); + } + } + } + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_uniforms(int ub_slot, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(ub_slot); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass, VALIDATE_AU_PASS_EXPECTED); + _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_AU_NO_PIPELINE); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); + SOKOL_ASSERT(pip && (pip->slot.id == _sg.cur_pipeline.id)); + SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); + + const _sg_shader_t* shd = pip->shader; + _SG_VALIDATE(shd->cmn.uniform_blocks[ub_slot].stage != SG_SHADERSTAGE_NONE, VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT); + _SG_VALIDATE(data->size == shd->cmn.uniform_blocks[ub_slot].size, VALIDATE_AU_SIZE); + + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_draw(int base_element, int num_elements, int num_instances) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(base_element); + _SOKOL_UNUSED(num_elements); + _SOKOL_UNUSED(num_instances); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && !_sg.cur_pass.is_compute, VALIDATE_DRAW_RENDERPASS_EXPECTED); + _SG_VALIDATE(base_element >= 0, VALIDATE_DRAW_BASEELEMENT); + _SG_VALIDATE(num_elements >= 0, VALIDATE_DRAW_NUMELEMENTS); + _SG_VALIDATE(num_instances >= 0, VALIDATE_DRAW_NUMINSTANCES); + _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(num_groups_x); + _SOKOL_UNUSED(num_groups_y); + _SOKOL_UNUSED(num_groups_z); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pass.in_pass && _sg.cur_pass.is_compute, VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED); + _SG_VALIDATE((num_groups_x >= 0) && (num_groups_x < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSX); + _SG_VALIDATE((num_groups_y >= 0) && (num_groups_y < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSY); + _SG_VALIDATE((num_groups_z >= 0) && (num_groups_z < (1<<16)), VALIDATE_DISPATCH_NUMGROUPSZ); + _SG_VALIDATE(_sg.required_bindings_and_uniforms == _sg.applied_bindings_and_uniforms, VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_update_buffer(const _sg_buffer_t* buf, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(buf); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(buf && data && data->ptr); + _sg_validate_begin(); + _SG_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_UPDATEBUF_USAGE); + _SG_VALIDATE(buf->cmn.size >= (int)data->size, VALIDATE_UPDATEBUF_SIZE); + _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_ONCE); + _SG_VALIDATE(buf->cmn.append_frame_index != _sg.frame_index, VALIDATE_UPDATEBUF_APPEND); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_append_buffer(const _sg_buffer_t* buf, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(buf); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(buf && data && data->ptr); + _sg_validate_begin(); + _SG_VALIDATE(buf->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_APPENDBUF_USAGE); + _SG_VALIDATE(buf->cmn.size >= (buf->cmn.append_pos + (int)data->size), VALIDATE_APPENDBUF_SIZE); + _SG_VALIDATE(buf->cmn.update_frame_index != _sg.frame_index, VALIDATE_APPENDBUF_UPDATE); + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_update_image(const _sg_image_t* img, const sg_image_data* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(img); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT(img && data); + _sg_validate_begin(); + _SG_VALIDATE(img->cmn.usage != SG_USAGE_IMMUTABLE, VALIDATE_UPDIMG_USAGE); + _SG_VALIDATE(img->cmn.upd_frame_index != _sg.frame_index, VALIDATE_UPDIMG_ONCE); + _sg_validate_image_data(data, + img->cmn.pixel_format, + img->cmn.width, + img->cmn.height, + (img->cmn.type == SG_IMAGETYPE_CUBE) ? 6 : 1, + img->cmn.num_mipmaps, + img->cmn.num_slices); + return _sg_validate_end(); + #endif +} + +// ██████ ███████ ███████ ██████ ██ ██ ██████ ██████ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ █████ ███████ ██ ██ ██ ██ ██████ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ██████ ██████ ██ ██ ██████ ███████ ███████ +// +// >>resources +_SOKOL_PRIVATE sg_buffer_desc _sg_buffer_desc_defaults(const sg_buffer_desc* desc) { + sg_buffer_desc def = *desc; + def.type = _sg_def(def.type, SG_BUFFERTYPE_VERTEXBUFFER); + def.usage = _sg_def(def.usage, SG_USAGE_IMMUTABLE); + if (def.size == 0) { + def.size = def.data.size; + } + return def; +} + +_SOKOL_PRIVATE sg_image_desc _sg_image_desc_defaults(const sg_image_desc* desc) { + sg_image_desc def = *desc; + def.type = _sg_def(def.type, SG_IMAGETYPE_2D); + def.num_slices = _sg_def(def.num_slices, 1); + def.num_mipmaps = _sg_def(def.num_mipmaps, 1); + def.usage = _sg_def(def.usage, SG_USAGE_IMMUTABLE); + if (desc->render_target) { + def.pixel_format = _sg_def(def.pixel_format, _sg.desc.environment.defaults.color_format); + def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count); + } else { + def.pixel_format = _sg_def(def.pixel_format, SG_PIXELFORMAT_RGBA8); + def.sample_count = _sg_def(def.sample_count, 1); + } + return def; +} + +_SOKOL_PRIVATE sg_sampler_desc _sg_sampler_desc_defaults(const sg_sampler_desc* desc) { + sg_sampler_desc def = *desc; + def.min_filter = _sg_def(def.min_filter, SG_FILTER_NEAREST); + def.mag_filter = _sg_def(def.mag_filter, SG_FILTER_NEAREST); + def.mipmap_filter = _sg_def(def.mipmap_filter, SG_FILTER_NEAREST); + def.wrap_u = _sg_def(def.wrap_u, SG_WRAP_REPEAT); + def.wrap_v = _sg_def(def.wrap_v, SG_WRAP_REPEAT); + def.wrap_w = _sg_def(def.wrap_w, SG_WRAP_REPEAT); + def.max_lod = _sg_def_flt(def.max_lod, FLT_MAX); + def.border_color = _sg_def(def.border_color, SG_BORDERCOLOR_OPAQUE_BLACK); + def.compare = _sg_def(def.compare, SG_COMPAREFUNC_NEVER); + def.max_anisotropy = _sg_def(def.max_anisotropy, 1); + return def; +} + +_SOKOL_PRIVATE sg_shader_desc _sg_shader_desc_defaults(const sg_shader_desc* desc) { + sg_shader_desc def = *desc; + #if defined(SOKOL_METAL) + def.vertex_func.entry = _sg_def(def.vertex_func.entry, "_main"); + def.fragment_func.entry = _sg_def(def.fragment_func.entry, "_main"); + def.compute_func.entry = _sg_def(def.compute_func.entry, "_main"); + #else + def.vertex_func.entry = _sg_def(def.vertex_func.entry, "main"); + def.fragment_func.entry = _sg_def(def.fragment_func.entry, "main"); + def.compute_func.entry = _sg_def(def.compute_func.entry, "main"); + #endif + #if defined(SOKOL_D3D11) + if (def.vertex_func.source) { + def.vertex_func.d3d11_target = _sg_def(def.vertex_func.d3d11_target, "vs_4_0"); + } + if (def.fragment_func.source) { + def.fragment_func.d3d11_target = _sg_def(def.fragment_func.d3d11_target, "ps_4_0"); + } + if (def.compute_func.source) { + def.compute_func.d3d11_target = _sg_def(def.fragment_func.d3d11_target,"cs_5_0"); + } + #endif + def.mtl_threads_per_threadgroup.y = _sg_def(desc->mtl_threads_per_threadgroup.y, 1); + def.mtl_threads_per_threadgroup.z = _sg_def(desc->mtl_threads_per_threadgroup.z, 1); + for (size_t ub_index = 0; ub_index < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_index++) { + sg_shader_uniform_block* ub_desc = &def.uniform_blocks[ub_index]; + if (ub_desc->stage != SG_SHADERSTAGE_NONE) { + ub_desc->layout = _sg_def(ub_desc->layout, SG_UNIFORMLAYOUT_NATIVE); + for (size_t u_index = 0; u_index < SG_MAX_UNIFORMBLOCK_MEMBERS; u_index++) { + sg_glsl_shader_uniform* u_desc = &ub_desc->glsl_uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { + break; + } + u_desc->array_count = _sg_def(u_desc->array_count, 1); + } + } + } + for (size_t img_index = 0; img_index < SG_MAX_IMAGE_BINDSLOTS; img_index++) { + sg_shader_image* img_desc = &def.images[img_index]; + if (img_desc->stage != SG_SHADERSTAGE_NONE) { + img_desc->image_type = _sg_def(img_desc->image_type, SG_IMAGETYPE_2D); + img_desc->sample_type = _sg_def(img_desc->sample_type, SG_IMAGESAMPLETYPE_FLOAT); + } + } + for (size_t smp_index = 0; smp_index < SG_MAX_SAMPLER_BINDSLOTS; smp_index++) { + sg_shader_sampler* smp_desc = &def.samplers[smp_index]; + if (smp_desc->stage != SG_SHADERSTAGE_NONE) { + smp_desc->sampler_type = _sg_def(smp_desc->sampler_type, SG_SAMPLERTYPE_FILTERING); + } + } + return def; +} + +_SOKOL_PRIVATE sg_pipeline_desc _sg_pipeline_desc_defaults(const sg_pipeline_desc* desc) { + sg_pipeline_desc def = *desc; + + // FIXME: should we actually do all this stuff for a compute pipeline? + + def.primitive_type = _sg_def(def.primitive_type, SG_PRIMITIVETYPE_TRIANGLES); + def.index_type = _sg_def(def.index_type, SG_INDEXTYPE_NONE); + def.cull_mode = _sg_def(def.cull_mode, SG_CULLMODE_NONE); + def.face_winding = _sg_def(def.face_winding, SG_FACEWINDING_CW); + def.sample_count = _sg_def(def.sample_count, _sg.desc.environment.defaults.sample_count); + + def.stencil.front.compare = _sg_def(def.stencil.front.compare, SG_COMPAREFUNC_ALWAYS); + def.stencil.front.fail_op = _sg_def(def.stencil.front.fail_op, SG_STENCILOP_KEEP); + def.stencil.front.depth_fail_op = _sg_def(def.stencil.front.depth_fail_op, SG_STENCILOP_KEEP); + def.stencil.front.pass_op = _sg_def(def.stencil.front.pass_op, SG_STENCILOP_KEEP); + def.stencil.back.compare = _sg_def(def.stencil.back.compare, SG_COMPAREFUNC_ALWAYS); + def.stencil.back.fail_op = _sg_def(def.stencil.back.fail_op, SG_STENCILOP_KEEP); + def.stencil.back.depth_fail_op = _sg_def(def.stencil.back.depth_fail_op, SG_STENCILOP_KEEP); + def.stencil.back.pass_op = _sg_def(def.stencil.back.pass_op, SG_STENCILOP_KEEP); + + def.depth.compare = _sg_def(def.depth.compare, SG_COMPAREFUNC_ALWAYS); + def.depth.pixel_format = _sg_def(def.depth.pixel_format, _sg.desc.environment.defaults.depth_format); + if (def.colors[0].pixel_format == SG_PIXELFORMAT_NONE) { + // special case depth-only rendering, enforce a color count of 0 + def.color_count = 0; + } else { + def.color_count = _sg_def(def.color_count, 1); + } + if (def.color_count > SG_MAX_COLOR_ATTACHMENTS) { + def.color_count = SG_MAX_COLOR_ATTACHMENTS; + } + for (int i = 0; i < def.color_count; i++) { + sg_color_target_state* cs = &def.colors[i]; + cs->pixel_format = _sg_def(cs->pixel_format, _sg.desc.environment.defaults.color_format); + cs->write_mask = _sg_def(cs->write_mask, SG_COLORMASK_RGBA); + sg_blend_state* bs = &def.colors[i].blend; + bs->op_rgb = _sg_def(bs->op_rgb, SG_BLENDOP_ADD); + bs->src_factor_rgb = _sg_def(bs->src_factor_rgb, SG_BLENDFACTOR_ONE); + if ((bs->op_rgb == SG_BLENDOP_MIN) || (bs->op_rgb == SG_BLENDOP_MAX)) { + bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ONE); + } else { + bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ZERO); + } + bs->op_alpha = _sg_def(bs->op_alpha, SG_BLENDOP_ADD); + bs->src_factor_alpha = _sg_def(bs->src_factor_alpha, SG_BLENDFACTOR_ONE); + if ((bs->op_alpha == SG_BLENDOP_MIN) || (bs->op_alpha == SG_BLENDOP_MAX)) { + bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ONE); + } else { + bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ZERO); + } + } + + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + sg_vertex_attr_state* a_state = &def.layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + sg_vertex_buffer_layout_state* l_state = &def.layout.buffers[a_state->buffer_index]; + l_state->step_func = _sg_def(l_state->step_func, SG_VERTEXSTEP_PER_VERTEX); + l_state->step_rate = _sg_def(l_state->step_rate, 1); + } + + // resolve vertex layout strides and offsets + int auto_offset[SG_MAX_VERTEXBUFFER_BINDSLOTS]; + _sg_clear(auto_offset, sizeof(auto_offset)); + bool use_auto_offset = true; + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + // to use computed offsets, *all* attr offsets must be 0 + if (def.layout.attrs[attr_index].offset != 0) { + use_auto_offset = false; + } + } + for (int attr_index = 0; attr_index < SG_MAX_VERTEX_ATTRIBUTES; attr_index++) { + sg_vertex_attr_state* a_state = &def.layout.attrs[attr_index]; + if (a_state->format == SG_VERTEXFORMAT_INVALID) { + break; + } + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEXBUFFER_BINDSLOTS); + if (use_auto_offset) { + a_state->offset = auto_offset[a_state->buffer_index]; + } + auto_offset[a_state->buffer_index] += _sg_vertexformat_bytesize(a_state->format); + } + // compute vertex strides if needed + for (int buf_index = 0; buf_index < SG_MAX_VERTEXBUFFER_BINDSLOTS; buf_index++) { + sg_vertex_buffer_layout_state* l_state = &def.layout.buffers[buf_index]; + if (l_state->stride == 0) { + l_state->stride = auto_offset[buf_index]; + } + } + + return def; +} + +_SOKOL_PRIVATE sg_attachments_desc _sg_attachments_desc_defaults(const sg_attachments_desc* desc) { + sg_attachments_desc def = *desc; + return def; +} + +_SOKOL_PRIVATE sg_buffer _sg_alloc_buffer(void) { + sg_buffer res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.buffer_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.buffer_pool, &_sg.pools.buffers[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(BUFFER_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_image _sg_alloc_image(void) { + sg_image res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.image_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.image_pool, &_sg.pools.images[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(IMAGE_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_sampler _sg_alloc_sampler(void) { + sg_sampler res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.sampler_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.sampler_pool, &_sg.pools.samplers[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(SAMPLER_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_shader _sg_alloc_shader(void) { + sg_shader res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.shader_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.shader_pool, &_sg.pools.shaders[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(SHADER_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_pipeline _sg_alloc_pipeline(void) { + sg_pipeline res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.pipeline_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id =_sg_slot_alloc(&_sg.pools.pipeline_pool, &_sg.pools.pipelines[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(PIPELINE_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE sg_attachments _sg_alloc_attachments(void) { + sg_attachments res; + int slot_index = _sg_pool_alloc_index(&_sg.pools.attachments_pool); + if (_SG_INVALID_SLOT_INDEX != slot_index) { + res.id = _sg_slot_alloc(&_sg.pools.attachments_pool, &_sg.pools.attachments[slot_index].slot, slot_index); + } else { + res.id = SG_INVALID_ID; + _SG_ERROR(PASS_POOL_EXHAUSTED); + } + return res; +} + +_SOKOL_PRIVATE void _sg_dealloc_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC) && (buf->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.buffer_pool, _sg_slot_index(buf->slot.id)); + _sg_slot_reset(&buf->slot); +} + +_SOKOL_PRIVATE void _sg_dealloc_image(_sg_image_t* img) { + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC) && (img->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.image_pool, _sg_slot_index(img->slot.id)); + _sg_slot_reset(&img->slot); +} + +_SOKOL_PRIVATE void _sg_dealloc_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC) && (smp->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.sampler_pool, _sg_slot_index(smp->slot.id)); + _sg_slot_reset(&smp->slot); +} + +_SOKOL_PRIVATE void _sg_dealloc_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC) && (shd->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.shader_pool, _sg_slot_index(shd->slot.id)); + _sg_slot_reset(&shd->slot); +} + +_SOKOL_PRIVATE void _sg_dealloc_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC) && (pip->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.pipeline_pool, _sg_slot_index(pip->slot.id)); + _sg_slot_reset(&pip->slot); +} + +_SOKOL_PRIVATE void _sg_dealloc_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts && (atts->slot.state == SG_RESOURCESTATE_ALLOC) && (atts->slot.id != SG_INVALID_ID)); + _sg_pool_free_index(&_sg.pools.attachments_pool, _sg_slot_index(atts->slot.id)); + _sg_slot_reset(&atts->slot); +} + +_SOKOL_PRIVATE void _sg_init_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_buffer_desc(desc)) { + _sg_buffer_common_init(&buf->cmn, desc); + buf->slot.state = _sg_create_buffer(buf, desc); + } else { + buf->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID)||(buf->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_image(_sg_image_t* img, const sg_image_desc* desc) { + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_image_desc(desc)) { + _sg_image_common_init(&img->cmn, desc); + img->slot.state = _sg_create_image(img, desc); + } else { + img->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID)||(img->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_sampler(_sg_sampler_t* smp, const sg_sampler_desc* desc) { + SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_sampler_desc(desc)) { + _sg_sampler_common_init(&smp->cmn, desc); + smp->slot.state = _sg_create_sampler(smp, desc); + } else { + smp->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID)||(smp->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_shader_desc(desc)) { + _sg_shader_common_init(&shd->cmn, desc); + shd->slot.state = _sg_create_shader(shd, desc); + } else { + shd->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID)||(shd->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_pipeline(_sg_pipeline_t* pip, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + if (_sg_validate_pipeline_desc(desc)) { + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, desc->shader.id); + if (shd && (shd->slot.state == SG_RESOURCESTATE_VALID)) { + _sg_pipeline_common_init(&pip->cmn, desc); + pip->slot.state = _sg_create_pipeline(pip, shd, desc); + } else { + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + } else { + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID)||(pip->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_init_attachments(_sg_attachments_t* atts, const sg_attachments_desc* desc) { + SOKOL_ASSERT(atts && atts->slot.state == SG_RESOURCESTATE_ALLOC); + SOKOL_ASSERT(desc); + if (_sg_validate_attachments_desc(desc)) { + // lookup pass attachment image pointers + _sg_image_t* color_images[SG_MAX_COLOR_ATTACHMENTS] = { 0 }; + _sg_image_t* resolve_images[SG_MAX_COLOR_ATTACHMENTS] = { 0 }; + _sg_image_t* ds_image = 0; + // NOTE: validation already checked that all surfaces are same width/height + int width = 0; + int height = 0; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (desc->colors[i].image.id) { + color_images[i] = _sg_lookup_image(&_sg.pools, desc->colors[i].image.id); + if (!(color_images[i] && color_images[i]->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + const int mip_level = desc->colors[i].mip_level; + width = _sg_miplevel_dim(color_images[i]->cmn.width, mip_level); + height = _sg_miplevel_dim(color_images[i]->cmn.height, mip_level); + } + if (desc->resolves[i].image.id) { + resolve_images[i] = _sg_lookup_image(&_sg.pools, desc->resolves[i].image.id); + if (!(resolve_images[i] && resolve_images[i]->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + } + } + if (desc->depth_stencil.image.id) { + ds_image = _sg_lookup_image(&_sg.pools, desc->depth_stencil.image.id); + if (!(ds_image && ds_image->slot.state == SG_RESOURCESTATE_VALID)) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + return; + } + const int mip_level = desc->depth_stencil.mip_level; + width = _sg_miplevel_dim(ds_image->cmn.width, mip_level); + height = _sg_miplevel_dim(ds_image->cmn.height, mip_level); + } + _sg_attachments_common_init(&atts->cmn, desc, width, height); + atts->slot.state = _sg_create_attachments(atts, color_images, resolve_images, ds_image, desc); + } else { + atts->slot.state = SG_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT((atts->slot.state == SG_RESOURCESTATE_VALID)||(atts->slot.state == SG_RESOURCESTATE_FAILED)); +} + +_SOKOL_PRIVATE void _sg_uninit_buffer(_sg_buffer_t* buf) { + SOKOL_ASSERT(buf && ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_buffer(buf); + _sg_reset_buffer_to_alloc_state(buf); +} + +_SOKOL_PRIVATE void _sg_uninit_image(_sg_image_t* img) { + SOKOL_ASSERT(img && ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_image(img); + _sg_reset_image_to_alloc_state(img); +} + +_SOKOL_PRIVATE void _sg_uninit_sampler(_sg_sampler_t* smp) { + SOKOL_ASSERT(smp && ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_sampler(smp); + _sg_reset_sampler_to_alloc_state(smp); +} + +_SOKOL_PRIVATE void _sg_uninit_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd && ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_shader(shd); + _sg_reset_shader_to_alloc_state(shd); +} + +_SOKOL_PRIVATE void _sg_uninit_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip && ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_pipeline(pip); + _sg_reset_pipeline_to_alloc_state(pip); +} + +_SOKOL_PRIVATE void _sg_uninit_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts && ((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED))); + _sg_discard_attachments(atts); + _sg_reset_attachments_to_alloc_state(atts); +} + +_SOKOL_PRIVATE void _sg_setup_commit_listeners(const sg_desc* desc) { + SOKOL_ASSERT(desc->max_commit_listeners > 0); + SOKOL_ASSERT(0 == _sg.commit_listeners.items); + SOKOL_ASSERT(0 == _sg.commit_listeners.num); + SOKOL_ASSERT(0 == _sg.commit_listeners.upper); + _sg.commit_listeners.num = desc->max_commit_listeners; + const size_t size = (size_t)_sg.commit_listeners.num * sizeof(sg_commit_listener); + _sg.commit_listeners.items = (sg_commit_listener*)_sg_malloc_clear(size); +} + +_SOKOL_PRIVATE void _sg_discard_commit_listeners(void) { + SOKOL_ASSERT(0 != _sg.commit_listeners.items); + _sg_free(_sg.commit_listeners.items); + _sg.commit_listeners.items = 0; +} + +_SOKOL_PRIVATE void _sg_notify_commit_listeners(void) { + SOKOL_ASSERT(_sg.commit_listeners.items); + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + const sg_commit_listener* listener = &_sg.commit_listeners.items[i]; + if (listener->func) { + listener->func(listener->user_data); + } + } +} + +_SOKOL_PRIVATE bool _sg_add_commit_listener(const sg_commit_listener* new_listener) { + SOKOL_ASSERT(new_listener && new_listener->func); + SOKOL_ASSERT(_sg.commit_listeners.items); + // first check if the listener hadn't been added already + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + const sg_commit_listener* slot = &_sg.commit_listeners.items[i]; + if ((slot->func == new_listener->func) && (slot->user_data == new_listener->user_data)) { + _SG_ERROR(IDENTICAL_COMMIT_LISTENER); + return false; + } + } + // first try to plug a hole + sg_commit_listener* slot = 0; + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + if (_sg.commit_listeners.items[i].func == 0) { + slot = &_sg.commit_listeners.items[i]; + break; + } + } + if (!slot) { + // append to end + if (_sg.commit_listeners.upper < _sg.commit_listeners.num) { + slot = &_sg.commit_listeners.items[_sg.commit_listeners.upper++]; + } + } + if (!slot) { + _SG_ERROR(COMMIT_LISTENER_ARRAY_FULL); + return false; + } + *slot = *new_listener; + return true; +} + +_SOKOL_PRIVATE bool _sg_remove_commit_listener(const sg_commit_listener* listener) { + SOKOL_ASSERT(listener && listener->func); + SOKOL_ASSERT(_sg.commit_listeners.items); + for (int i = 0; i < _sg.commit_listeners.upper; i++) { + sg_commit_listener* slot = &_sg.commit_listeners.items[i]; + // both the function pointer and user data must match! + if ((slot->func == listener->func) && (slot->user_data == listener->user_data)) { + slot->func = 0; + slot->user_data = 0; + // NOTE: since _sg_add_commit_listener() already catches duplicates, + // we don't need to worry about them here + return true; + } + } + return false; +} + +_SOKOL_PRIVATE void _sg_setup_compute(const sg_desc* desc) { + SOKOL_ASSERT(desc && (desc->max_dispatch_calls_per_pass > 0)); + const uint32_t max_tracked_sbufs = (uint32_t)desc->max_dispatch_calls_per_pass * SG_MAX_STORAGEBUFFER_BINDSLOTS; + _sg_tracker_init(&_sg.compute.readwrite_sbufs, max_tracked_sbufs); +} + +_SOKOL_PRIVATE void _sg_discard_compute(void) { + _sg_tracker_discard(&_sg.compute.readwrite_sbufs); +} + +_SOKOL_PRIVATE void _sg_compute_pass_track_storage_buffer(_sg_buffer_t* sbuf, bool readonly) { + SOKOL_ASSERT(sbuf); + if (!readonly) { + _sg_tracker_add(&_sg.compute.readwrite_sbufs, sbuf->slot.id); + } +} + +_SOKOL_PRIVATE void _sg_compute_on_endpass(void) { + SOKOL_ASSERT(_sg.cur_pass.in_pass); + SOKOL_ASSERT(_sg.cur_pass.is_compute); + _sg_tracker_reset(&_sg.compute.readwrite_sbufs); +} + +_SOKOL_PRIVATE sg_desc _sg_desc_defaults(const sg_desc* desc) { + /* + NOTE: on WebGPU, the default color pixel format MUST be provided, + cannot be a default compile-time constant. + */ + sg_desc res = *desc; + #if defined(SOKOL_WGPU) + SOKOL_ASSERT(SG_PIXELFORMAT_NONE < res.environment.defaults.color_format); + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + res.environment.defaults.color_format = _sg_def(res.environment.defaults.color_format, SG_PIXELFORMAT_BGRA8); + #else + res.environment.defaults.color_format = _sg_def(res.environment.defaults.color_format, SG_PIXELFORMAT_RGBA8); + #endif + res.environment.defaults.depth_format = _sg_def(res.environment.defaults.depth_format, SG_PIXELFORMAT_DEPTH_STENCIL); + res.environment.defaults.sample_count = _sg_def(res.environment.defaults.sample_count, 1); + res.buffer_pool_size = _sg_def(res.buffer_pool_size, _SG_DEFAULT_BUFFER_POOL_SIZE); + res.image_pool_size = _sg_def(res.image_pool_size, _SG_DEFAULT_IMAGE_POOL_SIZE); + res.sampler_pool_size = _sg_def(res.sampler_pool_size, _SG_DEFAULT_SAMPLER_POOL_SIZE); + res.shader_pool_size = _sg_def(res.shader_pool_size, _SG_DEFAULT_SHADER_POOL_SIZE); + res.pipeline_pool_size = _sg_def(res.pipeline_pool_size, _SG_DEFAULT_PIPELINE_POOL_SIZE); + res.attachments_pool_size = _sg_def(res.attachments_pool_size, _SG_DEFAULT_ATTACHMENTS_POOL_SIZE); + res.uniform_buffer_size = _sg_def(res.uniform_buffer_size, _SG_DEFAULT_UB_SIZE); + res.max_dispatch_calls_per_pass = _sg_def(res.max_dispatch_calls_per_pass, _SG_DEFAULT_MAX_DISPATCH_CALLS_PER_PASS); + res.max_commit_listeners = _sg_def(res.max_commit_listeners, _SG_DEFAULT_MAX_COMMIT_LISTENERS); + res.wgpu_bindgroups_cache_size = _sg_def(res.wgpu_bindgroups_cache_size, _SG_DEFAULT_WGPU_BINDGROUP_CACHE_SIZE); + return res; +} + +_SOKOL_PRIVATE sg_pass _sg_pass_defaults(const sg_pass* pass) { + sg_pass res = *pass; + if (!res.compute) { + if (res.compute && res.attachments.id == SG_INVALID_ID) { + // this is a swapchain-pass + res.swapchain.sample_count = _sg_def(res.swapchain.sample_count, _sg.desc.environment.defaults.sample_count); + res.swapchain.color_format = _sg_def(res.swapchain.color_format, _sg.desc.environment.defaults.color_format); + res.swapchain.depth_format = _sg_def(res.swapchain.depth_format, _sg.desc.environment.defaults.depth_format); + } + res.action = _sg_pass_action_defaults(&res.action); + } + return res; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sg_setup(const sg_desc* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->_start_canary == 0) && (desc->_end_canary == 0)); + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); + _sg.desc = _sg_desc_defaults(desc); + _sg_setup_pools(&_sg.pools, &_sg.desc); + _sg_setup_compute(&_sg.desc); + _sg_setup_commit_listeners(&_sg.desc); + _sg.frame_index = 1; + _sg.stats_enabled = true; + _sg_setup_backend(&_sg.desc); + _sg.valid = true; +} + +SOKOL_API_IMPL void sg_shutdown(void) { + _sg_discard_all_resources(&_sg.pools); + _sg_discard_backend(); + _sg_discard_commit_listeners(); + _sg_discard_compute(); + _sg_discard_pools(&_sg.pools); + _SG_CLEAR_ARC_STRUCT(_sg_state_t, _sg); +} + +SOKOL_API_IMPL bool sg_isvalid(void) { + return _sg.valid; +} + +SOKOL_API_IMPL sg_desc sg_query_desc(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.desc; +} + +SOKOL_API_IMPL sg_backend sg_query_backend(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.backend; +} + +SOKOL_API_IMPL sg_features sg_query_features(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.features; +} + +SOKOL_API_IMPL sg_limits sg_query_limits(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.limits; +} + +SOKOL_API_IMPL sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) { + SOKOL_ASSERT(_sg.valid); + int fmt_index = (int) fmt; + SOKOL_ASSERT((fmt_index > SG_PIXELFORMAT_NONE) && (fmt_index < _SG_PIXELFORMAT_NUM)); + const _sg_pixelformat_info_t* src = &_sg.formats[fmt_index]; + sg_pixelformat_info res; + _sg_clear(&res, sizeof(res)); + res.sample = src->sample; + res.filter = src->filter; + res.render = src->render; + res.blend = src->blend; + res.msaa = src->msaa; + res.depth = src->depth; + res.compressed = _sg_is_compressed_pixel_format(fmt); + if (!res.compressed) { + res.bytes_per_pixel = _sg_pixelformat_bytesize(fmt); + } + return res; +} + +SOKOL_API_IMPL int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(width > 0); + SOKOL_ASSERT((row_align_bytes > 0) && _sg_ispow2(row_align_bytes)); + SOKOL_ASSERT(((int)fmt > SG_PIXELFORMAT_NONE) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + return _sg_row_pitch(fmt, width, row_align_bytes); +} + +SOKOL_API_IMPL int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT((width > 0) && (height > 0)); + SOKOL_ASSERT((row_align_bytes > 0) && _sg_ispow2(row_align_bytes)); + SOKOL_ASSERT(((int)fmt > SG_PIXELFORMAT_NONE) && ((int)fmt < _SG_PIXELFORMAT_NUM)); + return _sg_surface_pitch(fmt, width, height, row_align_bytes); +} + +SOKOL_API_IMPL sg_frame_stats sg_query_frame_stats(void) { + SOKOL_ASSERT(_sg.valid); + return _sg.prev_stats; +} + +SOKOL_API_IMPL sg_trace_hooks sg_install_trace_hooks(const sg_trace_hooks* trace_hooks) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(trace_hooks); + _SOKOL_UNUSED(trace_hooks); + #if defined(SOKOL_TRACE_HOOKS) + sg_trace_hooks old_hooks = _sg.hooks; + _sg.hooks = *trace_hooks; + #else + static sg_trace_hooks old_hooks; + _SG_WARN(TRACE_HOOKS_NOT_ENABLED); + #endif + return old_hooks; +} + +SOKOL_API_IMPL sg_buffer sg_alloc_buffer(void) { + SOKOL_ASSERT(_sg.valid); + sg_buffer res = _sg_alloc_buffer(); + _SG_TRACE_ARGS(alloc_buffer, res); + return res; +} + +SOKOL_API_IMPL sg_image sg_alloc_image(void) { + SOKOL_ASSERT(_sg.valid); + sg_image res = _sg_alloc_image(); + _SG_TRACE_ARGS(alloc_image, res); + return res; +} + +SOKOL_API_IMPL sg_sampler sg_alloc_sampler(void) { + SOKOL_ASSERT(_sg.valid); + sg_sampler res = _sg_alloc_sampler(); + _SG_TRACE_ARGS(alloc_sampler, res); + return res; +} + +SOKOL_API_IMPL sg_shader sg_alloc_shader(void) { + SOKOL_ASSERT(_sg.valid); + sg_shader res = _sg_alloc_shader(); + _SG_TRACE_ARGS(alloc_shader, res); + return res; +} + +SOKOL_API_IMPL sg_pipeline sg_alloc_pipeline(void) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline res = _sg_alloc_pipeline(); + _SG_TRACE_ARGS(alloc_pipeline, res); + return res; +} + +SOKOL_API_IMPL sg_attachments sg_alloc_attachments(void) { + SOKOL_ASSERT(_sg.valid); + sg_attachments res = _sg_alloc_attachments(); + _SG_TRACE_ARGS(alloc_attachments, res); + return res; +} + +SOKOL_API_IMPL void sg_dealloc_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_buffer(buf); + } else { + _SG_ERROR(DEALLOC_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_dealloc_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_image(img); + } else { + _SG_ERROR(DEALLOC_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_image, img_id); +} + +SOKOL_API_IMPL void sg_dealloc_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_sampler(smp); + } else { + _SG_ERROR(DEALLOC_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_sampler, smp_id); +} + +SOKOL_API_IMPL void sg_dealloc_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_shader(shd); + } else { + _SG_ERROR(DEALLOC_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_shader, shd_id); +} + +SOKOL_API_IMPL void sg_dealloc_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pipeline(pip); + } else { + _SG_ERROR(DEALLOC_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_dealloc_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_attachments(atts); + } else { + _SG_ERROR(DEALLOC_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(dealloc_attachments, atts_id); +} + +SOKOL_API_IMPL void sg_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_buffer(buf, &desc_def); + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_buffer, buf_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_image(sg_image img_id, const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_image_desc desc_def = _sg_image_desc_defaults(desc); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_image, img_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_sampler(sg_sampler smp_id, const sg_sampler_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_sampler_desc desc_def = _sg_sampler_desc_defaults(desc); + _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_sampler(smp, &desc_def); + SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_sampler, smp_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_shader(sg_shader shd_id, const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_shader(shd, &desc_def); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_shader, shd_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_pipeline(pip, &desc_def); + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_pipeline, pip_id, &desc_def); +} + +SOKOL_API_IMPL void sg_init_attachments(sg_attachments atts_id, const sg_attachments_desc* desc) { + SOKOL_ASSERT(_sg.valid); + sg_attachments_desc desc_def = _sg_attachments_desc_defaults(desc); + _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_init_attachments(atts, &desc_def); + SOKOL_ASSERT((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)); + } else { + _SG_ERROR(INIT_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(init_attachments, atts_id, &desc_def); +} + +SOKOL_API_IMPL void sg_uninit_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_uninit_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_image, img_id); +} + +SOKOL_API_IMPL void sg_uninit_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + if ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_sampler(smp); + SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_sampler, smp_id); +} + +SOKOL_API_IMPL void sg_uninit_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_shader, shd_id); +} + +SOKOL_API_IMPL void sg_uninit_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_uninit_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + if ((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_attachments(atts); + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_ALLOC); + } else { + _SG_ERROR(UNINIT_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(uninit_attachments, atts_id); +} + +SOKOL_API_IMPL void sg_fail_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + buf->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_BUFFER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_buffer, buf_id); +} + +SOKOL_API_IMPL void sg_fail_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + img->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_IMAGE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_image, img_id); +} + +SOKOL_API_IMPL void sg_fail_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + smp->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_SAMPLER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_sampler, smp_id); +} + +SOKOL_API_IMPL void sg_fail_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + shd->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_SHADER_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_shader, shd_id); +} + +SOKOL_API_IMPL void sg_fail_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + pip->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_PIPELINE_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_fail_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + atts->slot.state = SG_RESOURCESTATE_FAILED; + } else { + _SG_ERROR(FAIL_ATTACHMENTS_INVALID_STATE); + } + } + _SG_TRACE_ARGS(fail_attachments, atts_id); +} + +SOKOL_API_IMPL sg_resource_state sg_query_buffer_state(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + sg_resource_state res = buf ? buf->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_image_state(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + sg_resource_state res = img ? img->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_sampler_state(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + sg_resource_state res = smp ? smp->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_shader_state(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + sg_resource_state res = shd ? shd->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_pipeline_state(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + sg_resource_state res = pip ? pip->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_resource_state sg_query_attachments_state(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + sg_resource_state res = atts ? atts->slot.state : SG_RESOURCESTATE_INVALID; + return res; +} + +SOKOL_API_IMPL sg_buffer sg_make_buffer(const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_buffer_desc desc_def = _sg_buffer_desc_defaults(desc); + sg_buffer buf_id = _sg_alloc_buffer(); + if (buf_id.id != SG_INVALID_ID) { + _sg_buffer_t* buf = _sg_buffer_at(&_sg.pools, buf_id.id); + SOKOL_ASSERT(buf && (buf->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_buffer(buf, &desc_def); + SOKOL_ASSERT((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_buffer, &desc_def, buf_id); + return buf_id; +} + +SOKOL_API_IMPL sg_image sg_make_image(const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_image_desc desc_def = _sg_image_desc_defaults(desc); + sg_image img_id = _sg_alloc_image(); + if (img_id.id != SG_INVALID_ID) { + _sg_image_t* img = _sg_image_at(&_sg.pools, img_id.id); + SOKOL_ASSERT(img && (img->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_image, &desc_def, img_id); + return img_id; +} + +SOKOL_API_IMPL sg_sampler sg_make_sampler(const sg_sampler_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_sampler_desc desc_def = _sg_sampler_desc_defaults(desc); + sg_sampler smp_id = _sg_alloc_sampler(); + if (smp_id.id != SG_INVALID_ID) { + _sg_sampler_t* smp = _sg_sampler_at(&_sg.pools, smp_id.id); + SOKOL_ASSERT(smp && (smp->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_sampler(smp, &desc_def); + SOKOL_ASSERT((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_sampler, &desc_def, smp_id); + return smp_id; +} + +SOKOL_API_IMPL sg_shader sg_make_shader(const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_shader_desc desc_def = _sg_shader_desc_defaults(desc); + sg_shader shd_id = _sg_alloc_shader(); + if (shd_id.id != SG_INVALID_ID) { + _sg_shader_t* shd = _sg_shader_at(&_sg.pools, shd_id.id); + SOKOL_ASSERT(shd && (shd->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_shader(shd, &desc_def); + SOKOL_ASSERT((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_shader, &desc_def, shd_id); + return shd_id; +} + +SOKOL_API_IMPL sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_pipeline_desc desc_def = _sg_pipeline_desc_defaults(desc); + sg_pipeline pip_id = _sg_alloc_pipeline(); + if (pip_id.id != SG_INVALID_ID) { + _sg_pipeline_t* pip = _sg_pipeline_at(&_sg.pools, pip_id.id); + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_pipeline(pip, &desc_def); + SOKOL_ASSERT((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_pipeline, &desc_def, pip_id); + return pip_id; +} + +SOKOL_API_IMPL sg_attachments sg_make_attachments(const sg_attachments_desc* desc) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(desc); + sg_attachments_desc desc_def = _sg_attachments_desc_defaults(desc); + sg_attachments atts_id = _sg_alloc_attachments(); + if (atts_id.id != SG_INVALID_ID) { + _sg_attachments_t* atts = _sg_attachments_at(&_sg.pools, atts_id.id); + SOKOL_ASSERT(atts && (atts->slot.state == SG_RESOURCESTATE_ALLOC)); + _sg_init_attachments(atts, &desc_def); + SOKOL_ASSERT((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)); + } + _SG_TRACE_ARGS(make_attachments, &desc_def, atts_id); + return atts_id; +} + +SOKOL_API_IMPL void sg_destroy_buffer(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_buffer, buf_id); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + if ((buf->slot.state == SG_RESOURCESTATE_VALID) || (buf->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (buf->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_buffer(buf); + SOKOL_ASSERT(buf->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_image(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_image, img_id); + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + if ((img->slot.state == SG_RESOURCESTATE_VALID) || (img->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (img->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_image(img); + SOKOL_ASSERT(img->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_sampler(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_sampler, smp_id); + _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + if ((smp->slot.state == SG_RESOURCESTATE_VALID) || (smp->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_sampler(smp); + SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (smp->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_sampler(smp); + SOKOL_ASSERT(smp->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_shader(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_shader, shd_id); + _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + if ((shd->slot.state == SG_RESOURCESTATE_VALID) || (shd->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (shd->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_shader(shd); + SOKOL_ASSERT(shd->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_pipeline, pip_id); + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if ((pip->slot.state == SG_RESOURCESTATE_VALID) || (pip->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (pip->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_pipeline(pip); + SOKOL_ASSERT(pip->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_destroy_attachments(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + _SG_TRACE_ARGS(destroy_attachments, atts_id); + _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + if ((atts->slot.state == SG_RESOURCESTATE_VALID) || (atts->slot.state == SG_RESOURCESTATE_FAILED)) { + _sg_uninit_attachments(atts); + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_ALLOC); + } + if (atts->slot.state == SG_RESOURCESTATE_ALLOC) { + _sg_dealloc_attachments(atts); + SOKOL_ASSERT(atts->slot.state == SG_RESOURCESTATE_INITIAL); + } + } +} + +SOKOL_API_IMPL void sg_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(!_sg.cur_pass.valid); + SOKOL_ASSERT(!_sg.cur_pass.in_pass); + SOKOL_ASSERT(pass); + SOKOL_ASSERT((pass->_start_canary == 0) && (pass->_end_canary == 0)); + const sg_pass pass_def = _sg_pass_defaults(pass); + if (!_sg_validate_begin_pass(&pass_def)) { + return; + } + if (!pass_def.compute) { + if (pass_def.attachments.id != SG_INVALID_ID) { + // an offscreen pass + SOKOL_ASSERT(_sg.cur_pass.atts == 0); + _sg.cur_pass.atts = _sg_lookup_attachments(&_sg.pools, pass_def.attachments.id); + if (0 == _sg.cur_pass.atts) { + _SG_ERROR(BEGINPASS_ATTACHMENT_INVALID); + return; + } + _sg.cur_pass.atts_id = pass_def.attachments; + _sg.cur_pass.width = _sg.cur_pass.atts->cmn.width; + _sg.cur_pass.height = _sg.cur_pass.atts->cmn.height; + } else { + // a swapchain pass + SOKOL_ASSERT(pass_def.swapchain.width > 0); + SOKOL_ASSERT(pass_def.swapchain.height > 0); + SOKOL_ASSERT(pass_def.swapchain.color_format > SG_PIXELFORMAT_NONE); + SOKOL_ASSERT(pass_def.swapchain.sample_count > 0); + _sg.cur_pass.width = pass_def.swapchain.width; + _sg.cur_pass.height = pass_def.swapchain.height; + _sg.cur_pass.swapchain.color_fmt = pass_def.swapchain.color_format; + _sg.cur_pass.swapchain.depth_fmt = pass_def.swapchain.depth_format; + _sg.cur_pass.swapchain.sample_count = pass_def.swapchain.sample_count; + } + } + _sg.cur_pass.valid = true; // may be overruled by backend begin-pass functions + _sg.cur_pass.in_pass = true; + _sg.cur_pass.is_compute = pass_def.compute; + _sg_begin_pass(&pass_def); + _SG_TRACE_ARGS(begin_pass, &pass_def); +} + +SOKOL_API_IMPL void sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_apply_viewport(x, y, width, height, origin_top_left)) { + return; + } + #endif + _sg_stats_add(num_apply_viewport, 1); + if (!_sg.cur_pass.valid) { + return; + } + _sg_apply_viewport(x, y, width, height, origin_top_left); + _SG_TRACE_ARGS(apply_viewport, x, y, width, height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) { + sg_apply_viewport((int)x, (int)y, (int)width, (int)height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_apply_scissor_rect(x, y, width, height, origin_top_left)) { + return; + } + #endif + _sg_stats_add(num_apply_scissor_rect, 1); + if (!_sg.cur_pass.valid) { + return; + } + _sg_apply_scissor_rect(x, y, width, height, origin_top_left); + _SG_TRACE_ARGS(apply_scissor_rect, x, y, width, height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) { + sg_apply_scissor_rect((int)x, (int)y, (int)width, (int)height, origin_top_left); +} + +SOKOL_API_IMPL void sg_apply_pipeline(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + _sg_stats_add(num_apply_pipeline, 1); + if (!_sg_validate_apply_pipeline(pip_id)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.valid) { + return; + } + _sg.cur_pipeline = pip_id; + _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + SOKOL_ASSERT(pip); + + _sg.next_draw_valid = (SG_RESOURCESTATE_VALID == pip->slot.state); + if (!_sg.next_draw_valid) { + return; + } + + SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); + _sg_apply_pipeline(pip); + + // set the expected bindings and uniform block flags + _sg.required_bindings_and_uniforms = pip->cmn.required_bindings_and_uniforms | pip->shader->cmn.required_bindings_and_uniforms; + _sg.applied_bindings_and_uniforms = 0; + + _SG_TRACE_ARGS(apply_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_apply_bindings(const sg_bindings* bindings) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(bindings); + SOKOL_ASSERT((bindings->_start_canary == 0) && (bindings->_end_canary==0)); + _sg_stats_add(num_apply_bindings, 1); + _sg.applied_bindings_and_uniforms |= (1 << SG_MAX_UNIFORMBLOCK_BINDSLOTS); + if (!_sg_validate_apply_bindings(bindings)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + + _sg_bindings_t bnd; + _sg_clear(&bnd, sizeof(bnd)); + bnd.pip = _sg_lookup_pipeline(&_sg.pools, _sg.cur_pipeline.id); + if (0 == bnd.pip) { + _sg.next_draw_valid = false; + } + SOKOL_ASSERT(bnd.pip->shader && (bnd.pip->cmn.shader_id.id == bnd.pip->shader->slot.id)); + const _sg_shader_t* shd = bnd.pip->shader; + + if (!_sg.cur_pass.is_compute) { + for (size_t i = 0; i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + if (bnd.pip->cmn.vertex_buffer_layout_active[i]) { + SOKOL_ASSERT(bindings->vertex_buffers[i].id != SG_INVALID_ID); + bnd.vbs[i] = _sg_lookup_buffer(&_sg.pools, bindings->vertex_buffers[i].id); + bnd.vb_offsets[i] = bindings->vertex_buffer_offsets[i]; + if (bnd.vbs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.vbs[i]->slot.state); + _sg.next_draw_valid &= !bnd.vbs[i]->cmn.append_overflow; + } else { + _sg.next_draw_valid = false; + } + } + } + if (bindings->index_buffer.id) { + bnd.ib = _sg_lookup_buffer(&_sg.pools, bindings->index_buffer.id); + bnd.ib_offset = bindings->index_buffer_offset; + if (bnd.ib) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.ib->slot.state); + _sg.next_draw_valid &= !bnd.ib->cmn.append_overflow; + } else { + _sg.next_draw_valid = false; + } + } + } + + for (int i = 0; i < SG_MAX_IMAGE_BINDSLOTS; i++) { + if (shd->cmn.images[i].stage != SG_SHADERSTAGE_NONE) { + SOKOL_ASSERT(bindings->images[i].id != SG_INVALID_ID); + bnd.imgs[i] = _sg_lookup_image(&_sg.pools, bindings->images[i].id); + if (bnd.imgs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.imgs[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } + } + + for (size_t i = 0; i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + if (shd->cmn.samplers[i].stage != SG_SHADERSTAGE_NONE) { + SOKOL_ASSERT(bindings->samplers[i].id != SG_INVALID_ID); + bnd.smps[i] = _sg_lookup_sampler(&_sg.pools, bindings->samplers[i].id); + if (bnd.smps[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.smps[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } + } + + for (size_t i = 0; i < SG_MAX_STORAGEBUFFER_BINDSLOTS; i++) { + if (shd->cmn.storage_buffers[i].stage != SG_SHADERSTAGE_NONE) { + SOKOL_ASSERT(bindings->storage_buffers[i].id != SG_INVALID_ID); + bnd.sbufs[i] = _sg_lookup_buffer(&_sg.pools, bindings->storage_buffers[i].id); + if (bnd.sbufs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.sbufs[i]->slot.state); + if (_sg.cur_pass.is_compute) { + _sg_compute_pass_track_storage_buffer(bnd.sbufs[i], shd->cmn.storage_buffers[i].readonly); + } + } else { + _sg.next_draw_valid = false; + } + } + } + + if (_sg.next_draw_valid) { + _sg.next_draw_valid &= _sg_apply_bindings(&bnd); + _SG_TRACE_ARGS(apply_bindings, bindings); + } +} + +SOKOL_API_IMPL void sg_apply_uniforms(int ub_slot, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT((ub_slot >= 0) && (ub_slot < SG_MAX_UNIFORMBLOCK_BINDSLOTS)); + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + _sg_stats_add(num_apply_uniforms, 1); + _sg_stats_add(size_apply_uniforms, (uint32_t)data->size); + _sg.applied_bindings_and_uniforms |= 1 << ub_slot; + if (!_sg_validate_apply_uniforms(ub_slot, data)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + _sg_apply_uniforms(ub_slot, data); + _SG_TRACE_ARGS(apply_uniforms, ub_slot, data); +} + +SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_draw(base_element, num_elements, num_instances)) { + return; + } + #endif + _sg_stats_add(num_draw, 1); + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + // skip no-op draws + if ((0 == num_elements) || (0 == num_instances)) { + return; + } + _sg_draw(base_element, num_elements, num_instances); + _SG_TRACE_ARGS(draw, base_element, num_elements, num_instances); +} + +SOKOL_API_IMPL void sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) { + SOKOL_ASSERT(_sg.valid); + #if defined(SOKOL_DEBUG) + if (!_sg_validate_dispatch(num_groups_x, num_groups_y, num_groups_z)) { + return; + } + #endif + _sg_stats_add(num_dispatch, 1); + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + // skip no-op dispatches + if ((0 == num_groups_x) || (0 == num_groups_y) || (0 == num_groups_z)) { + return; + } + _sg_dispatch(num_groups_x, num_groups_y, num_groups_z); + _SG_TRACE_ARGS(dispatch, num_groups_x, num_groups_y, num_groups_z); +} + +SOKOL_API_IMPL void sg_end_pass(void) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + _sg_stats_add(num_passes, 1); + // NOTE: don't exit early if !_sg.cur_pass.valid + _sg_end_pass(); + _sg.cur_pipeline.id = SG_INVALID_ID; + if (_sg.cur_pass.is_compute) { + _sg_compute_on_endpass(); + } + _sg_clear(&_sg.cur_pass, sizeof(_sg.cur_pass)); + _SG_TRACE_NOARGS(end_pass); +} + +SOKOL_API_IMPL void sg_commit(void) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(!_sg.cur_pass.valid); + SOKOL_ASSERT(!_sg.cur_pass.in_pass); + _sg_commit(); + _sg.stats.frame_index = _sg.frame_index; + _sg.prev_stats = _sg.stats; + _sg_clear(&_sg.stats, sizeof(_sg.stats)); + _sg_notify_commit_listeners(); + _SG_TRACE_NOARGS(commit); + _sg.frame_index++; +} + +SOKOL_API_IMPL void sg_reset_state_cache(void) { + SOKOL_ASSERT(_sg.valid); + _sg_reset_state_cache(); + _SG_TRACE_NOARGS(reset_state_cache); +} + +SOKOL_API_IMPL void sg_update_buffer(sg_buffer buf_id, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(data && data->ptr && (data->size > 0)); + _sg_stats_add(num_update_buffer, 1); + _sg_stats_add(size_update_buffer, (uint32_t)data->size); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if ((data->size > 0) && buf && (buf->slot.state == SG_RESOURCESTATE_VALID)) { + if (_sg_validate_update_buffer(buf, data)) { + SOKOL_ASSERT(data->size <= (size_t)buf->cmn.size); + // only one update allowed per buffer and frame + SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index); + // update and append on same buffer in same frame not allowed + SOKOL_ASSERT(buf->cmn.append_frame_index != _sg.frame_index); + _sg_update_buffer(buf, data); + buf->cmn.update_frame_index = _sg.frame_index; + } + } + _SG_TRACE_ARGS(update_buffer, buf_id, data); +} + +SOKOL_API_IMPL int sg_append_buffer(sg_buffer buf_id, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(data && data->ptr); + _sg_stats_add(num_append_buffer, 1); + _sg_stats_add(size_append_buffer, (uint32_t)data->size); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + int result; + if (buf) { + // rewind append cursor in a new frame + if (buf->cmn.append_frame_index != _sg.frame_index) { + buf->cmn.append_pos = 0; + buf->cmn.append_overflow = false; + } + if (((size_t)buf->cmn.append_pos + data->size) > (size_t)buf->cmn.size) { + buf->cmn.append_overflow = true; + } + const int start_pos = buf->cmn.append_pos; + // NOTE: the multiple-of-4 requirement for the buffer offset is coming + // from WebGPU, but we want identical behaviour between backends + SOKOL_ASSERT(_sg_multiple_u64((uint64_t)start_pos, 4)); + if (buf->slot.state == SG_RESOURCESTATE_VALID) { + if (_sg_validate_append_buffer(buf, data)) { + if (!buf->cmn.append_overflow && (data->size > 0)) { + // update and append on same buffer in same frame not allowed + SOKOL_ASSERT(buf->cmn.update_frame_index != _sg.frame_index); + _sg_append_buffer(buf, data, buf->cmn.append_frame_index != _sg.frame_index); + buf->cmn.append_pos += (int) _sg_roundup_u64(data->size, 4); + buf->cmn.append_frame_index = _sg.frame_index; + } + } + } + result = start_pos; + } else { + // FIXME: should we return -1 here? + result = 0; + } + _SG_TRACE_ARGS(append_buffer, buf_id, data, result); + return result; +} + +SOKOL_API_IMPL bool sg_query_buffer_overflow(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + bool result = buf ? buf->cmn.append_overflow : false; + return result; +} + +SOKOL_API_IMPL bool sg_query_buffer_will_overflow(sg_buffer buf_id, size_t size) { + SOKOL_ASSERT(_sg.valid); + _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + bool result = false; + if (buf) { + int append_pos = buf->cmn.append_pos; + // rewind append cursor in a new frame + if (buf->cmn.append_frame_index != _sg.frame_index) { + append_pos = 0; + } + if ((append_pos + _sg_roundup((int)size, 4)) > buf->cmn.size) { + result = true; + } + } + return result; +} + +SOKOL_API_IMPL void sg_update_image(sg_image img_id, const sg_image_data* data) { + SOKOL_ASSERT(_sg.valid); + _sg_stats_add(num_update_image, 1); + for (int face_index = 0; face_index < SG_CUBEFACE_NUM; face_index++) { + for (int mip_index = 0; mip_index < SG_MAX_MIPMAPS; mip_index++) { + if (data->subimage[face_index][mip_index].size == 0) { + break; + } + _sg_stats_add(size_update_image, (uint32_t)data->subimage[face_index][mip_index].size); + } + } + _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { + if (_sg_validate_update_image(img, data)) { + SOKOL_ASSERT(img->cmn.upd_frame_index != _sg.frame_index); + _sg_update_image(img, data); + img->cmn.upd_frame_index = _sg.frame_index; + } + } + _SG_TRACE_ARGS(update_image, img_id, data); +} + +SOKOL_API_IMPL void sg_push_debug_group(const char* name) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(name); + _sg_push_debug_group(name); + _SG_TRACE_ARGS(push_debug_group, name); +} + +SOKOL_API_IMPL void sg_pop_debug_group(void) { + SOKOL_ASSERT(_sg.valid); + _sg_pop_debug_group(); + _SG_TRACE_NOARGS(pop_debug_group); +} + +SOKOL_API_IMPL bool sg_add_commit_listener(sg_commit_listener listener) { + SOKOL_ASSERT(_sg.valid); + return _sg_add_commit_listener(&listener); +} + +SOKOL_API_IMPL bool sg_remove_commit_listener(sg_commit_listener listener) { + SOKOL_ASSERT(_sg.valid); + return _sg_remove_commit_listener(&listener); +} + +SOKOL_API_IMPL void sg_enable_frame_stats(void) { + SOKOL_ASSERT(_sg.valid); + _sg.stats_enabled = true; +} + +SOKOL_API_IMPL void sg_disable_frame_stats(void) { + SOKOL_ASSERT(_sg.valid); + _sg.stats_enabled = false; +} + +SOKOL_API_IMPL bool sg_frame_stats_enabled(void) { + return _sg.stats_enabled; +} + +SOKOL_API_IMPL sg_buffer_info sg_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_info info; + _sg_clear(&info, sizeof(info)); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + info.slot.state = buf->slot.state; + info.slot.res_id = buf->slot.id; + info.update_frame_index = buf->cmn.update_frame_index; + info.append_frame_index = buf->cmn.append_frame_index; + info.append_pos = buf->cmn.append_pos; + info.append_overflow = buf->cmn.append_overflow; + #if defined(SOKOL_D3D11) + info.num_slots = 1; + info.active_slot = 0; + #else + info.num_slots = buf->cmn.num_slots; + info.active_slot = buf->cmn.active_slot; + #endif + } + return info; +} + +SOKOL_API_IMPL sg_image_info sg_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_info info; + _sg_clear(&info, sizeof(info)); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + info.slot.state = img->slot.state; + info.slot.res_id = img->slot.id; + info.upd_frame_index = img->cmn.upd_frame_index; + #if defined(SOKOL_D3D11) + info.num_slots = 1; + info.active_slot = 0; + #else + info.num_slots = img->cmn.num_slots; + info.active_slot = img->cmn.active_slot; + #endif + } + return info; +} + +SOKOL_API_IMPL sg_sampler_info sg_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_sampler_info info; + _sg_clear(&info, sizeof(info)); + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + info.slot.state = smp->slot.state; + info.slot.res_id = smp->slot.id; + } + return info; +} + +SOKOL_API_IMPL sg_shader_info sg_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_shader_info info; + _sg_clear(&info, sizeof(info)); + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + info.slot.state = shd->slot.state; + info.slot.res_id = shd->slot.id; + } + return info; +} + +SOKOL_API_IMPL sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_info info; + _sg_clear(&info, sizeof(info)); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + info.slot.state = pip->slot.state; + info.slot.res_id = pip->slot.id; + } + return info; +} + +SOKOL_API_IMPL sg_attachments_info sg_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_attachments_info info; + _sg_clear(&info, sizeof(info)); + const _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + info.slot.state = atts->slot.state; + info.slot.res_id = atts->slot.id; + } + return info; +} + +SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_desc(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_buffer_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + desc.size = (size_t)buf->cmn.size; + desc.type = buf->cmn.type; + desc.usage = buf->cmn.usage; + } + return desc; +} + +SOKOL_API_IMPL size_t sg_query_buffer_size(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + return (size_t)buf->cmn.size; + } + return 0; +} + +SOKOL_API_IMPL sg_buffer_type sg_query_buffer_type(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + return buf->cmn.type; + } + return _SG_BUFFERTYPE_DEFAULT; +} + +SOKOL_API_IMPL sg_usage sg_query_buffer_usage(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + return buf->cmn.usage; + } + return _SG_USAGE_DEFAULT; +} + +SOKOL_API_IMPL sg_image_desc sg_query_image_desc(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_image_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + desc.type = img->cmn.type; + desc.render_target = img->cmn.render_target; + desc.width = img->cmn.width; + desc.height = img->cmn.height; + desc.num_slices = img->cmn.num_slices; + desc.num_mipmaps = img->cmn.num_mipmaps; + desc.usage = img->cmn.usage; + desc.pixel_format = img->cmn.pixel_format; + desc.sample_count = img->cmn.sample_count; + } + return desc; +} + +SOKOL_API_IMPL sg_image_type sg_query_image_type(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.type; + } + return _SG_IMAGETYPE_DEFAULT; +} + +SOKOL_API_IMPL int sg_query_image_width(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.width; + } + return 0; +} + +SOKOL_API_IMPL int sg_query_image_height(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.height; + } + return 0; +} + +SOKOL_API_IMPL int sg_query_image_num_slices(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.num_slices; + } + return 0; +} + +SOKOL_API_IMPL int sg_query_image_num_mipmaps(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.num_mipmaps; + } + return 0; +} + +SOKOL_API_IMPL sg_pixel_format sg_query_image_pixelformat(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.pixel_format; + } + return _SG_PIXELFORMAT_DEFAULT; +} + +SOKOL_API_IMPL sg_usage sg_query_image_usage(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.usage; + } + return _SG_USAGE_DEFAULT; +} + +SOKOL_API_IMPL int sg_query_image_sample_count(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(_sg.valid); + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + return img->cmn.sample_count; + } + return 0; +} + +SOKOL_API_IMPL sg_sampler_desc sg_query_sampler_desc(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_sampler_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + desc.min_filter = smp->cmn.min_filter; + desc.mag_filter = smp->cmn.mag_filter; + desc.mipmap_filter = smp->cmn.mipmap_filter; + desc.wrap_u = smp->cmn.wrap_u; + desc.wrap_v = smp->cmn.wrap_v; + desc.wrap_w = smp->cmn.wrap_w; + desc.min_lod = smp->cmn.min_lod; + desc.max_lod = smp->cmn.max_lod; + desc.border_color = smp->cmn.border_color; + desc.compare = smp->cmn.compare; + desc.max_anisotropy = smp->cmn.max_anisotropy; + } + return desc; +} + +SOKOL_API_IMPL sg_shader_desc sg_query_shader_desc(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_shader_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + for (size_t ub_idx = 0; ub_idx < SG_MAX_UNIFORMBLOCK_BINDSLOTS; ub_idx++) { + sg_shader_uniform_block* ub_desc = &desc.uniform_blocks[ub_idx]; + const _sg_shader_uniform_block_t* ub = &shd->cmn.uniform_blocks[ub_idx]; + ub_desc->stage = ub->stage; + ub_desc->size = ub->size; + } + for (size_t sbuf_idx = 0; sbuf_idx < SG_MAX_STORAGEBUFFER_BINDSLOTS; sbuf_idx++) { + sg_shader_storage_buffer* sbuf_desc = &desc.storage_buffers[sbuf_idx]; + const _sg_shader_storage_buffer_t* sbuf = &shd->cmn.storage_buffers[sbuf_idx]; + sbuf_desc->stage = sbuf->stage; + sbuf_desc->readonly = sbuf->readonly; + } + for (size_t img_idx = 0; img_idx < SG_MAX_IMAGE_BINDSLOTS; img_idx++) { + sg_shader_image* img_desc = &desc.images[img_idx]; + const _sg_shader_image_t* img = &shd->cmn.images[img_idx]; + img_desc->stage = img->stage; + img_desc->image_type = img->image_type; + img_desc->sample_type = img->sample_type; + img_desc->multisampled = img->multisampled; + } + for (size_t smp_idx = 0; smp_idx < SG_MAX_SAMPLER_BINDSLOTS; smp_idx++) { + sg_shader_sampler* smp_desc = &desc.samplers[smp_idx]; + const _sg_shader_sampler_t* smp = &shd->cmn.samplers[smp_idx]; + smp_desc->stage = smp->stage; + smp_desc->sampler_type = smp->sampler_type; + } + for (size_t img_smp_idx = 0; img_smp_idx < SG_MAX_IMAGE_SAMPLER_PAIRS; img_smp_idx++) { + sg_shader_image_sampler_pair* img_smp_desc = &desc.image_sampler_pairs[img_smp_idx]; + const _sg_shader_image_sampler_t* img_smp = &shd->cmn.image_samplers[img_smp_idx]; + img_smp_desc->stage = img_smp->stage; + img_smp_desc->image_slot = img_smp->image_slot; + img_smp_desc->sampler_slot = img_smp->sampler_slot; + } + } + return desc; +} + +SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_pipeline_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + desc.compute = pip->cmn.is_compute; + desc.shader = pip->cmn.shader_id; + desc.layout = pip->cmn.layout; + desc.depth = pip->cmn.depth; + desc.stencil = pip->cmn.stencil; + desc.color_count = pip->cmn.color_count; + for (int i = 0; i < pip->cmn.color_count; i++) { + desc.colors[i] = pip->cmn.colors[i]; + } + desc.primitive_type = pip->cmn.primitive_type; + desc.index_type = pip->cmn.index_type; + desc.cull_mode = pip->cmn.cull_mode; + desc.face_winding = pip->cmn.face_winding; + desc.sample_count = pip->cmn.sample_count; + desc.blend_color = pip->cmn.blend_color; + desc.alpha_to_coverage_enabled = pip->cmn.alpha_to_coverage_enabled; + } + return desc; +} + +SOKOL_API_IMPL sg_attachments_desc sg_query_attachments_desc(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_attachments_desc desc; + _sg_clear(&desc, sizeof(desc)); + const _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + for (int i = 0; i < atts->cmn.num_colors; i++) { + desc.colors[i].image = atts->cmn.colors[i].image_id; + desc.colors[i].mip_level = atts->cmn.colors[i].mip_level; + desc.colors[i].slice = atts->cmn.colors[i].slice; + } + desc.depth_stencil.image = atts->cmn.depth_stencil.image_id; + desc.depth_stencil.mip_level = atts->cmn.depth_stencil.mip_level; + desc.depth_stencil.slice = atts->cmn.depth_stencil.slice; + } + return desc; +} + +SOKOL_API_IMPL sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_buffer_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_image_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_sampler_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_shader_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_pipeline_desc_defaults(desc); +} + +SOKOL_API_IMPL sg_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc* desc) { + SOKOL_ASSERT(_sg.valid && desc); + return _sg_attachments_desc_defaults(desc); +} + +SOKOL_API_IMPL const void* sg_d3d11_device(void) { + #if defined(SOKOL_D3D11) + return (const void*) _sg.d3d11.dev; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_d3d11_device_context(void) { + #if defined(SOKOL_D3D11) + return (const void*) _sg.d3d11.ctx; + #else + return 0; + #endif +} + +SOKOL_API_IMPL sg_d3d11_buffer_info sg_d3d11_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + res.buf = (const void*) buf->d3d11.buf; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_image_info sg_d3d11_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + res.tex2d = (const void*) img->d3d11.tex2d; + res.tex3d = (const void*) img->d3d11.tex3d; + res.res = (const void*) img->d3d11.res; + res.srv = (const void*) img->d3d11.srv; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_sampler_info sg_d3d11_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + res.smp = (const void*) smp->d3d11.smp; + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_shader_info sg_d3d11_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + for (size_t i = 0; i < SG_MAX_UNIFORMBLOCK_BINDSLOTS; i++) { + res.cbufs[i] = (const void*) shd->d3d11.all_cbufs[i]; + } + res.vs = (const void*) shd->d3d11.vs; + res.fs = (const void*) shd->d3d11.fs; + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_pipeline_info sg_d3d11_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_pipeline_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + res.il = (const void*) pip->d3d11.il; + res.rs = (const void*) pip->d3d11.rs; + res.dss = (const void*) pip->d3d11.dss; + res.bs = (const void*) pip->d3d11.bs; + } + #else + _SOKOL_UNUSED(pip_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_d3d11_attachments_info sg_d3d11_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_d3d11_attachments_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_D3D11) + const _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + res.color_rtv[i] = (const void*) atts->d3d11.colors[i].view.rtv; + res.resolve_rtv[i] = (const void*) atts->d3d11.resolves[i].view.rtv; + } + res.dsv = (const void*) atts->d3d11.depth_stencil.view.dsv; + } + #else + _SOKOL_UNUSED(atts_id); + #endif + return res; +} + +SOKOL_API_IMPL const void* sg_mtl_device(void) { + #if defined(SOKOL_METAL) + if (nil != _sg.mtl.device) { + return (__bridge const void*) _sg.mtl.device; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_mtl_render_command_encoder(void) { + #if defined(SOKOL_METAL) + if (nil != _sg.mtl.render_cmd_encoder) { + return (__bridge const void*) _sg.mtl.render_cmd_encoder; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_mtl_compute_command_encoder(void) { + #if defined(SOKOL_METAL) + if (nil != _sg.mtl.compute_cmd_encoder) { + return (__bridge const void*) _sg.mtl.compute_cmd_encoder; + } else { + return 0; + } + #else + return 0; + #endif +} + +SOKOL_API_IMPL sg_mtl_buffer_info sg_mtl_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + if (buf->mtl.buf[i] != 0) { + res.buf[i] = (__bridge void*) _sg_mtl_id(buf->mtl.buf[i]); + } + } + res.active_slot = buf->cmn.active_slot; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_image_info sg_mtl_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + if (img->mtl.tex[i] != 0) { + res.tex[i] = (__bridge void*) _sg_mtl_id(img->mtl.tex[i]); + } + } + res.active_slot = img->cmn.active_slot; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_sampler_info sg_mtl_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + if (smp->mtl.sampler_state != 0) { + res.smp = (__bridge void*) _sg_mtl_id(smp->mtl.sampler_state); + } + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_shader_info sg_mtl_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + const int vertex_lib = shd->mtl.vertex_func.mtl_lib; + const int vertex_func = shd->mtl.vertex_func.mtl_func; + const int fragment_lib = shd->mtl.fragment_func.mtl_lib; + const int fragment_func = shd->mtl.fragment_func.mtl_func; + if (vertex_lib != 0) { + res.vertex_lib = (__bridge void*) _sg_mtl_id(vertex_lib); + } + if (fragment_lib != 0) { + res.fragment_lib = (__bridge void*) _sg_mtl_id(fragment_lib); + } + if (vertex_func != 0) { + res.vertex_func = (__bridge void*) _sg_mtl_id(vertex_func); + } + if (fragment_func != 0) { + res.fragment_func = (__bridge void*) _sg_mtl_id(fragment_func); + } + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_mtl_pipeline_info sg_mtl_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_mtl_pipeline_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_METAL) + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + if (pip->mtl.rps != 0) { + res.rps = (__bridge void*) _sg_mtl_id(pip->mtl.rps); + } + if (pip->mtl.dss != 0) { + res.dss = (__bridge void*) _sg_mtl_id(pip->mtl.dss); + } + } + #else + _SOKOL_UNUSED(pip_id); + #endif + return res; +} + +SOKOL_API_IMPL const void* sg_wgpu_device(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.dev; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_queue(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.queue; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_command_encoder(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.cmd_enc; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_render_pass_encoder(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.rpass_enc; + #else + return 0; + #endif +} + +SOKOL_API_IMPL const void* sg_wgpu_compute_pass_encoder(void) { + #if defined(SOKOL_WGPU) + return (const void*) _sg.wgpu.cpass_enc; + #else + return 0; + #endif +} + +SOKOL_API_IMPL sg_wgpu_buffer_info sg_wgpu_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + res.buf = (const void*) buf->wgpu.buf; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_image_info sg_wgpu_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + res.tex = (const void*) img->wgpu.tex; + res.view = (const void*) img->wgpu.view; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_sampler_info sg_wgpu_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + res.smp = (const void*) smp->wgpu.smp; + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_shader_info sg_wgpu_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + res.vs_mod = (const void*) shd->wgpu.vertex_func.module; + res.fs_mod = (const void*) shd->wgpu.fragment_func.module; + res.bgl = (const void*) shd->wgpu.bgl_img_smp_sbuf; + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_pipeline_info sg_wgpu_query_pipeline_info(sg_pipeline pip_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_pipeline_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_pipeline_t* pip = _sg_lookup_pipeline(&_sg.pools, pip_id.id); + if (pip) { + res.render_pipeline = (const void*) pip->wgpu.rpip; + res.compute_pipeline = (const void*) pip->wgpu.cpip; + } + #else + _SOKOL_UNUSED(pip_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_wgpu_attachments_info sg_wgpu_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_wgpu_attachments_info res; + _sg_clear(&res, sizeof(res)); + #if defined(SOKOL_WGPU) + const _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + res.color_view[i] = (const void*) atts->wgpu.colors[i].view; + res.resolve_view[i] = (const void*) atts->wgpu.resolves[i].view; + } + res.ds_view = (const void*) atts->wgpu.depth_stencil.view; + } + #else + _SOKOL_UNUSED(atts_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_buffer_info sg_gl_query_buffer_info(sg_buffer buf_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_buffer_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_buffer_t* buf = _sg_lookup_buffer(&_sg.pools, buf_id.id); + if (buf) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + res.buf[i] = buf->gl.buf[i]; + } + res.active_slot = buf->cmn.active_slot; + } + #else + _SOKOL_UNUSED(buf_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_image_info sg_gl_query_image_info(sg_image img_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_image_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id); + if (img) { + for (int i = 0; i < SG_NUM_INFLIGHT_FRAMES; i++) { + res.tex[i] = img->gl.tex[i]; + } + res.tex_target = img->gl.target; + res.msaa_render_buffer = img->gl.msaa_render_buffer; + res.active_slot = img->cmn.active_slot; + } + #else + _SOKOL_UNUSED(img_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_sampler_info sg_gl_query_sampler_info(sg_sampler smp_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_sampler_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, smp_id.id); + if (smp) { + res.smp = smp->gl.smp; + } + #else + _SOKOL_UNUSED(smp_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_shader_info sg_gl_query_shader_info(sg_shader shd_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_shader_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_shader_t* shd = _sg_lookup_shader(&_sg.pools, shd_id.id); + if (shd) { + res.prog = shd->gl.prog; + } + #else + _SOKOL_UNUSED(shd_id); + #endif + return res; +} + +SOKOL_API_IMPL sg_gl_attachments_info sg_gl_query_attachments_info(sg_attachments atts_id) { + SOKOL_ASSERT(_sg.valid); + sg_gl_attachments_info res; + _sg_clear(&res, sizeof(res)); + #if defined(_SOKOL_ANY_GL) + const _sg_attachments_t* atts = _sg_lookup_attachments(&_sg.pools, atts_id.id); + if (atts) { + res.framebuffer = atts->gl.fb; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + res.msaa_resolve_framebuffer[i] = atts->gl.msaa_resolve_framebuffer[i]; + } + } + #else + _SOKOL_UNUSED(atts_id); + #endif + return res; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SOKOL_GFX_IMPL diff --git a/modules/sokol-jai/sokol/c/sokol_gl.c b/modules/sokol-jai/sokol/c/sokol_gl.c new file mode 100644 index 0000000..f15e4a7 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_gl.c @@ -0,0 +1,6 @@ +#if defined(IMPL) +#define SOKOL_GL_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_gfx.h" +#include "sokol_gl.h" diff --git a/modules/sokol-jai/sokol/c/sokol_gl.h b/modules/sokol-jai/sokol/c/sokol_gl.h new file mode 100644 index 0000000..b419b15 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_gl.h @@ -0,0 +1,4811 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GL_IMPL) +#define SOKOL_GL_IMPL +#endif +#ifndef SOKOL_GL_INCLUDED +/* + sokol_gl.h -- OpenGL 1.x style rendering on top of sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GL_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_GL_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GL_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + If sokol_gl.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GL_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before including sokol_gl.h: + + sokol_gfx.h + + Matrix functions have been taken from MESA and Regal. + + FEATURE OVERVIEW: + ================= + sokol_gl.h implements a subset of the OpenGLES 1.x feature set useful for + when you just want to quickly render a bunch of triangles or + lines without having to mess with buffers and shaders. + + The current feature set is mostly useful for debug visualizations + and simple UI-style 2D rendering: + + What's implemented: + - vertex components: + - position (x, y, z) + - 2D texture coords (u, v) + - color (r, g, b, a) + - primitive types: + - triangle list and strip + - line list and strip + - quad list (TODO: quad strips) + - point list + - one texture layer (no multi-texturing) + - viewport and scissor-rect with selectable origin (top-left or bottom-left) + - all GL 1.x matrix stack functions, and additionally equivalent + functions for gluPerspective and gluLookat + + Notable GLES 1.x features that are *NOT* implemented: + - vertex lighting (this is the most likely GL feature that might be added later) + - vertex arrays (although providing whole chunks of vertex data at once + might be a useful feature for a later version) + - texture coordinate generation + - line width + - all pixel store functions + - no ALPHA_TEST + - no clear functions (clearing is handled by the sokol-gfx render pass) + - fog + + Notable differences to GL: + - No "enum soup" for render states etc, instead there's a + 'pipeline stack', this is similar to GL's matrix stack, + but for pipeline-state-objects. The pipeline object at + the top of the pipeline stack defines the active set of render states + - All angles are in radians, not degrees (note the sgl_rad() and + sgl_deg() conversion functions) + - No enable/disable state for scissor test, this is always enabled + + STEP BY STEP: + ============= + --- To initialize sokol-gl, call: + + sgl_setup(const sgl_desc_t* desc) + + NOTE that sgl_setup() must be called *after* initializing sokol-gfx + (via sg_setup). This is because sgl_setup() needs to create + sokol-gfx resource objects. + + If you're intending to render to the default pass, and also don't + want to tweak memory usage, and don't want any logging output you can + just keep sgl_desc_t zero-initialized: + + sgl_setup(&(sgl_desc_t*){ 0 }); + + In this case, sokol-gl will create internal sg_pipeline objects that + are compatible with the sokol-app default framebuffer. + + I would recommend to at least install a logging callback so that + you'll see any warnings and errors. The easiest way is through + sokol_log.h: + + #include "sokol_log.h" + + sgl_setup(&(sgl_desc_t){ + .logger.func = slog_func. + }); + + If you want to render into a framebuffer with different pixel-format + and MSAA attributes you need to provide the matching attributes in the + sgl_setup() call: + + sgl_setup(&(sgl_desc_t*){ + .color_format = SG_PIXELFORMAT_..., + .depth_format = SG_PIXELFORMAT_..., + .sample_count = ..., + }); + + To reduce memory usage, or if you need to create more then the default number of + contexts, pipelines, vertices or draw commands, set the following sgl_desc_t + members: + + .context_pool_size (default: 4) + .pipeline_pool_size (default: 64) + .max_vertices (default: 64k) + .max_commands (default: 16k) + + Finally you can change the face winding for front-facing triangles + and quads: + + .face_winding - default is SG_FACEWINDING_CCW + + The default winding for front faces is counter-clock-wise. This is + the same as OpenGL's default, but different from sokol-gfx. + + --- Optionally create additional context objects if you want to render into + multiple sokol-gfx render passes (or generally if you want to + use multiple independent sokol-gl "state buckets") + + sgl_context ctx = sgl_make_context(const sgl_context_desc_t* desc) + + For details on rendering with sokol-gl contexts, search below for + WORKING WITH CONTEXTS. + + --- Optionally create pipeline-state-objects if you need render state + that differs from sokol-gl's default state: + + sgl_pipeline pip = sgl_make_pipeline(const sg_pipeline_desc* desc) + + ...this creates a pipeline object that's compatible with the currently + active context, alternatively call: + + sgl_pipeline_pip = sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc) + + ...to create a pipeline object that's compatible with an explicitly + provided context. + + The similarity with sokol_gfx.h's sg_pipeline type and sg_make_pipeline() + function is intended. sgl_make_pipeline() also takes a standard + sokol-gfx sg_pipeline_desc object to describe the render state, but + without: + - shader + - vertex layout + - color- and depth-pixel-formats + - primitive type (lines, triangles, ...) + - MSAA sample count + Those will be filled in by sgl_make_pipeline(). Note that each + call to sgl_make_pipeline() needs to create several sokol-gfx + pipeline objects (one for each primitive type). + + 'depth.write_enabled' will be forced to 'false' if the context this + pipeline object is intended for has its depth pixel format set to + SG_PIXELFORMAT_NONE (which means the framebuffer this context is used + with doesn't have a depth-stencil surface). + + --- if you need to destroy sgl_pipeline objects before sgl_shutdown(): + + sgl_destroy_pipeline(sgl_pipeline pip) + + --- After sgl_setup() you can call any of the sokol-gl functions anywhere + in a frame, *except* sgl_draw(). The 'vanilla' functions + will only change internal sokol-gl state, and not call any sokol-gfx + functions. + + --- Unlike OpenGL, sokol-gl has a function to reset internal state to + a known default. This is useful at the start of a sequence of + rendering operations: + + void sgl_defaults(void) + + This will set the following default state: + + - current texture coordinate to u=0.0f, v=0.0f + - current color to white (rgba all 1.0f) + - current point size to 1.0f + - unbind the current texture and texturing will be disabled + - *all* matrices will be set to identity (also the projection matrix) + - the default render state will be set by loading the 'default pipeline' + into the top of the pipeline stack + + The current matrix- and pipeline-stack-depths will not be changed by + sgl_defaults(). + + --- change the currently active renderstate through the + pipeline-stack functions, this works similar to the + traditional GL matrix stack: + + ...load the default pipeline state on the top of the pipeline stack: + + sgl_load_default_pipeline() + + ...load a specific pipeline on the top of the pipeline stack: + + sgl_load_pipeline(sgl_pipeline pip) + + ...push and pop the pipeline stack: + sgl_push_pipeline() + sgl_pop_pipeline() + + --- control texturing with: + + sgl_enable_texture() + sgl_disable_texture() + sgl_texture(sg_image img, sg_sampler smp) + + NOTE: the img and smp handles can be invalid (SG_INVALID_ID), in this + case, sokol-gl will fall back to the internal default (white) texture + and sampler. + + --- set the current viewport and scissor rect with: + + sgl_viewport(int x, int y, int w, int h, bool origin_top_left) + sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) + + ...or call these alternatives which take float arguments (this might allow + to avoid casting between float and integer in more strongly typed languages + when floating point pixel coordinates are used): + + sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) + sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) + + ...these calls add a new command to the internal command queue, so + that the viewport or scissor rect are set at the right time relative + to other sokol-gl calls. + + --- adjust the transform matrices, matrix manipulation works just like + the OpenGL matrix stack: + + ...set the current matrix mode: + + sgl_matrix_mode_modelview() + sgl_matrix_mode_projection() + sgl_matrix_mode_texture() + + ...load the identity matrix into the current matrix: + + sgl_load_identity() + + ...translate, rotate and scale the current matrix: + + sgl_translate(float x, float y, float z) + sgl_rotate(float angle_rad, float x, float y, float z) + sgl_scale(float x, float y, float z) + + NOTE that all angles in sokol-gl are in radians, not in degree. + Convert between radians and degree with the helper functions: + + float sgl_rad(float deg) - degrees to radians + float sgl_deg(float rad) - radians to degrees + + ...directly load the current matrix from a float[16] array: + + sgl_load_matrix(const float m[16]) + sgl_load_transpose_matrix(const float m[16]) + + ...directly multiply the current matrix from a float[16] array: + + sgl_mult_matrix(const float m[16]) + sgl_mult_transpose_matrix(const float m[16]) + + The memory layout of those float[16] arrays is the same as in OpenGL. + + ...more matrix functions: + + sgl_frustum(float left, float right, float bottom, float top, float near, float far) + sgl_ortho(float left, float right, float bottom, float top, float near, float far) + sgl_perspective(float fov_y, float aspect, float near, float far) + sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) + + These functions work the same as glFrustum(), glOrtho(), gluPerspective() + and gluLookAt(). + + ...and finally to push / pop the current matrix stack: + + sgl_push_matrix(void) + sgl_pop_matrix(void) + + Again, these work the same as glPushMatrix() and glPopMatrix(). + + --- perform primitive rendering: + + ...set the current texture coordinate and color 'registers' with or + point size with: + + sgl_t2f(float u, float v) - set current texture coordinate + sgl_c*(...) - set current color + sgl_point_size(float size) - set current point size + + There are several functions for setting the color (as float values, + unsigned byte values, packed as unsigned 32-bit integer, with + and without alpha). + + NOTE that these are the only functions that can be called both inside + sgl_begin_*() / sgl_end() and outside. + + Also NOTE that point size is currently hardwired to 1.0f if the D3D11 + backend is used. + + ...start a primitive vertex sequence with: + + sgl_begin_points() + sgl_begin_lines() + sgl_begin_line_strip() + sgl_begin_triangles() + sgl_begin_triangle_strip() + sgl_begin_quads() + + ...after sgl_begin_*() specify vertices: + + sgl_v*(...) + sgl_v*_t*(...) + sgl_v*_c*(...) + sgl_v*_t*_c*(...) + + These functions write a new vertex to sokol-gl's internal vertex buffer, + optionally with texture-coords and color. If the texture coordinate + and/or color is missing, it will be taken from the current texture-coord + and color 'register'. + + ...finally, after specifying vertices, call: + + sgl_end() + + This will record a new draw command in sokol-gl's internal command + list, or it will extend the previous draw command if no relevant + state has changed since the last sgl_begin/end pair. + + --- inside a sokol-gfx rendering pass, call the sgl_draw() function + to render the currently active context: + + sgl_draw() + + ...or alternatively call: + + sgl_context_draw(ctx) + + ...to render an explicitly provided context. + + This will render everything that has been recorded in the context since + the last call to sgl_draw() through sokol-gfx, and will 'rewind' the internal + vertex-, uniform- and command-buffers. + + --- each sokol-gl context tracks internal error states which can + be obtains via: + + sgl_error_t sgl_error() + + ...alternatively with an explicit context argument: + + sgl_error_t sgl_context_error(ctx); + + ...this returns a struct with the following booleans: + + .any - true if any of the below errors is true + .vertices_full - internal vertex buffer is full (checked in sgl_end()) + .uniforms_full - the internal uniforms buffer is full (checked in sgl_end()) + .commands_full - the internal command buffer is full (checked in sgl_end()) + .stack_overflow - matrix- or pipeline-stack overflow + .stack_underflow - matrix- or pipeline-stack underflow + .no_context - the active context no longer exists + + ...depending on the above error state, sgl_draw() may skip rendering + completely, or only draw partial geometry + + --- you can get the number of recorded vertices and draw commands in the current + frame and active sokol-gl context via: + + int sgl_num_vertices() + int sgl_num_commands() + + ...this allows you to check whether the vertex or command pools are running + full before the overflow actually happens (in this case you could also + check the error booleans in the result of sgl_error()). + + RENDER LAYERS + ============= + Render layers allow to split sokol-gl rendering into separate draw-command + groups which can then be rendered separately in a sokol-gfx draw pass. This + allows to mix/interleave sokol-gl rendering with other render operations. + + Layered rendering is controlled through two functions: + + sgl_layer(int layer_id) + sgl_draw_layer(int layer_id) + + (and the context-variant sgl_draw_layer(): sgl_context_draw_layer() + + The sgl_layer() function sets the 'current layer', any sokol-gl calls + which internally record draw commands will also store the current layer + in the draw command, and later in a sokol-gfx render pass, a call + to sgl_draw_layer() will only render the draw commands that have + a matching layer. + + The default layer is '0', this is active after sokol-gl setup, and + is also restored at the start of a new frame (but *not* by calling + sgl_defaults()). + + NOTE that calling sgl_draw() is equivalent with sgl_draw_layer(0) + (in general you should either use either use sgl_draw() or + sgl_draw_layer() in an application, but not both). + + WORKING WITH CONTEXTS: + ====================== + If you want to render to more than one sokol-gfx render pass you need to + work with additional sokol-gl context objects (one context object for + each offscreen rendering pass, in addition to the implicitly created + 'default context'. + + All sokol-gl state is tracked per context, and there is always a "current + context" (with the notable exception that the currently set context is + destroyed, more on that later). + + Using multiple contexts can also be useful if you only render in + a single pass, but want to maintain multiple independent "state buckets". + + To create new context object, call: + + sgl_context ctx = sgl_make_context(&(sgl_context_desc){ + .max_vertices = ..., // default: 64k + .max_commands = ..., // default: 16k + .color_format = ..., + .depth_format = ..., + .sample_count = ..., + }); + + The color_format, depth_format and sample_count items must be compatible + with the render pass the sgl_draw() or sgL_context_draw() function + will be called in. + + Creating a context does *not* make the context current. To do this, call: + + sgl_set_context(ctx); + + The currently active context will implicitly be used by most sokol-gl functions + which don't take an explicit context handle as argument. + + To switch back to the default context, pass the global constant SGL_DEFAULT_CONTEXT: + + sgl_set_context(SGL_DEFAULT_CONTEXT); + + ...or alternatively use the function sgl_default_context() instead of the + global constant: + + sgl_set_context(sgl_default_context()); + + To get the currently active context, call: + + sgl_context cur_ctx = sgl_get_context(); + + The following functions exist in two variants, one which use the currently + active context (set with sgl_set_context()), and another version which + takes an explicit context handle instead: + + sgl_make_pipeline() vs sgl_context_make_pipeline() + sgl_error() vs sgl_context_error(); + sgl_draw() vs sgl_context_draw(); + + Except for using the currently active context versus a provided context + handle, the two variants are exactlyidentical, e.g. the following + code sequences do the same thing: + + sgl_set_context(ctx); + sgl_pipeline pip = sgl_make_pipeline(...); + sgl_error_t err = sgl_error(); + sgl_draw(); + + vs + + sgl_pipeline pip = sgl_context_make_pipeline(ctx, ...); + sgl_error_t err = sgl_context_error(ctx); + sgl_context_draw(ctx); + + Destroying the currently active context is a 'soft error'. All following + calls which require a currently active context will silently fail, + and sgl_error() will return SGL_ERROR_NO_CONTEXT. + + UNDER THE HOOD: + =============== + sokol_gl.h works by recording vertex data and rendering commands into + memory buffers, and then drawing the recorded commands via sokol_gfx.h + + The only functions which call into sokol_gfx.h are: + - sgl_setup() + - sgl_shutdown() + - sgl_draw() (and variants) + + sgl_setup() must be called after initializing sokol-gfx. + sgl_shutdown() must be called before shutting down sokol-gfx. + sgl_draw() must be called once per frame inside a sokol-gfx render pass. + + All other sokol-gl function can be called anywhere in a frame, since + they just record data into memory buffers owned by sokol-gl. + + What happens in: + + sgl_setup(): + Unique resources shared by all contexts are created: + - a shader object (using embedded shader source or byte code) + - an 8x8 white default texture + The default context is created, which involves: + - 3 memory buffers are created, one for vertex data, + one for uniform data, and one for commands + - a dynamic vertex buffer is created + - the default sgl_pipeline object is created, which involves + creating 5 sg_pipeline objects + + One vertex is 24 bytes: + - float3 position + - float2 texture coords + - uint32_t color + + One uniform block is 128 bytes: + - mat4 model-view-projection matrix + - mat4 texture matrix + + One draw command is ca. 24 bytes for the actual + command code plus command arguments. + + Each sgl_end() consumes one command, and one uniform block + (only when the matrices have changed). + The required size for one sgl_begin/end pair is (at most): + + (152 + 24 * num_verts) bytes + + sgl_shutdown(): + - all sokol-gfx resources (buffer, shader, default-texture and + all pipeline objects) are destroyed + - the 3 memory buffers are freed + + sgl_draw() (and variants) + - copy all recorded vertex data into the dynamic sokol-gfx buffer + via a call to sg_update_buffer() + - for each recorded command: + - if the layer number stored in the command doesn't match + the layer that's to be rendered, skip to the next + command + - if it's a viewport command, call sg_apply_viewport() + - if it's a scissor-rect command, call sg_apply_scissor_rect() + - if it's a draw command: + - depending on what has changed since the last draw command, + call sg_apply_pipeline(), sg_apply_bindings() and + sg_apply_uniforms() + - finally call sg_draw() + + All other functions only modify the internally tracked state, add + data to the vertex, uniform and command buffers, or manipulate + the matrix stack. + + ON DRAW COMMAND MERGING + ======================= + Not every call to sgl_end() will automatically record a new draw command. + If possible, the previous draw command will simply be extended, + resulting in fewer actual draw calls later in sgl_draw(). + + A draw command will be merged with the previous command if "no relevant + state has changed" since the last sgl_end(), meaning: + + - no calls to sgl_viewport() and sgl_scissor_rect() + - the primitive type hasn't changed + - the primitive type isn't a 'strip type' (no line or triangle strip) + - the pipeline state object hasn't changed + - the current layer hasn't changed + - none of the matrices has changed + - none of the texture state has changed + + Merging a draw command simply means that the number of vertices + to render in the previous draw command will be incremented by the + number of vertices in the new draw command. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sgl_setup(&(sgl_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sgl_setup(&(sgl_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sgl' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SGL_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gl.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gl like this: + + sgl_setup(&(sgl_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_GL_INCLUDED (1) +#include +#include +#include // size_t, offsetof + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_gl.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GL_API_DECL) +#define SOKOL_GL_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GL_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GL_IMPL) +#define SOKOL_GL_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GL_API_DECL __declspec(dllimport) +#else +#define SOKOL_GL_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sgl_log_item_t + + Log items are defined via X-Macros, and expanded to an + enum 'sgl_log_item' - and in debug mode only - corresponding strings. + + Used as parameter in the logging callback. +*/ +#define _SGL_LOG_ITEMS \ + _SGL_LOGITEM_XMACRO(OK, "Ok") \ + _SGL_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SGL_LOGITEM_XMACRO(MAKE_PIPELINE_FAILED, "sg_make_pipeline() failed") \ + _SGL_LOGITEM_XMACRO(PIPELINE_POOL_EXHAUSTED, "pipeline pool exhausted (use sgl_desc_t.pipeline_pool_size to adjust)") \ + _SGL_LOGITEM_XMACRO(ADD_COMMIT_LISTENER_FAILED, "sg_add_commit_listener() failed") \ + _SGL_LOGITEM_XMACRO(CONTEXT_POOL_EXHAUSTED, "context pool exhausted (use sgl_desc_t.context_pool_size to adjust)") \ + _SGL_LOGITEM_XMACRO(CANNOT_DESTROY_DEFAULT_CONTEXT, "cannot destroy default context") \ + +#define _SGL_LOGITEM_XMACRO(item,msg) SGL_LOGITEM_##item, +typedef enum sgl_log_item_t { + _SGL_LOG_ITEMS +} sgl_log_item_t; +#undef _SGL_LOGITEM_XMACRO + +/* + sgl_logger_t + + Used in sgl_desc_t to provide a custom logging and error reporting + callback to sokol-gl. +*/ +typedef struct sgl_logger_t { + void (*func)( + const char* tag, // always "sgl" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SGL_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gl.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sgl_logger_t; + +/* sokol_gl pipeline handle (created with sgl_make_pipeline()) */ +typedef struct sgl_pipeline { uint32_t id; } sgl_pipeline; + +/* a context handle (created with sgl_make_context()) */ +typedef struct sgl_context { uint32_t id; } sgl_context; + +/* + sgl_error_t + + Errors are reset each frame after calling sgl_draw(), + get the last error code with sgl_error() +*/ +typedef struct sgl_error_t { + bool any; + bool vertices_full; + bool uniforms_full; + bool commands_full; + bool stack_overflow; + bool stack_underflow; + bool no_context; +} sgl_error_t; + +/* + sgl_context_desc_t + + Describes the initialization parameters of a rendering context. + Creating additional contexts is useful if you want to render + in separate sokol-gfx passes. +*/ +typedef struct sgl_context_desc_t { + int max_vertices; // default: 64k + int max_commands; // default: 16k + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; +} sgl_context_desc_t; + +/* + sgl_allocator_t + + Used in sgl_desc_t to provide custom memory-alloc and -free functions + to sokol_gl.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sgl_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sgl_allocator_t; + +typedef struct sgl_desc_t { + int max_vertices; // default: 64k + int max_commands; // default: 16k + int context_pool_size; // max number of contexts (including default context), default: 4 + int pipeline_pool_size; // size of internal pipeline pool, default: 64 + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + sg_face_winding face_winding; // default: SG_FACEWINDING_CCW + sgl_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + sgl_logger_t logger; // optional log function override (default: NO LOGGING) +} sgl_desc_t; + +/* the default context handle */ +static const sgl_context SGL_DEFAULT_CONTEXT = { 0x00010001 }; + +/* setup/shutdown/misc */ +SOKOL_GL_API_DECL void sgl_setup(const sgl_desc_t* desc); +SOKOL_GL_API_DECL void sgl_shutdown(void); +SOKOL_GL_API_DECL float sgl_rad(float deg); +SOKOL_GL_API_DECL float sgl_deg(float rad); +SOKOL_GL_API_DECL sgl_error_t sgl_error(void); +SOKOL_GL_API_DECL sgl_error_t sgl_context_error(sgl_context ctx); + +/* context functions */ +SOKOL_GL_API_DECL sgl_context sgl_make_context(const sgl_context_desc_t* desc); +SOKOL_GL_API_DECL void sgl_destroy_context(sgl_context ctx); +SOKOL_GL_API_DECL void sgl_set_context(sgl_context ctx); +SOKOL_GL_API_DECL sgl_context sgl_get_context(void); +SOKOL_GL_API_DECL sgl_context sgl_default_context(void); + +/* get information about recorded vertices and commands in current context */ +SOKOL_GL_API_DECL int sgl_num_vertices(void); +SOKOL_GL_API_DECL int sgl_num_commands(void); + +/* draw recorded commands (call inside a sokol-gfx render pass) */ +SOKOL_GL_API_DECL void sgl_draw(void); +SOKOL_GL_API_DECL void sgl_context_draw(sgl_context ctx); +SOKOL_GL_API_DECL void sgl_draw_layer(int layer_id); +SOKOL_GL_API_DECL void sgl_context_draw_layer(sgl_context ctx, int layer_id); + +/* create and destroy pipeline objects */ +SOKOL_GL_API_DECL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc); +SOKOL_GL_API_DECL sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc); +SOKOL_GL_API_DECL void sgl_destroy_pipeline(sgl_pipeline pip); + +/* render state functions */ +SOKOL_GL_API_DECL void sgl_defaults(void); +SOKOL_GL_API_DECL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left); +SOKOL_GL_API_DECL void sgl_enable_texture(void); +SOKOL_GL_API_DECL void sgl_disable_texture(void); +SOKOL_GL_API_DECL void sgl_texture(sg_image img, sg_sampler smp); +SOKOL_GL_API_DECL void sgl_layer(int layer_id); + +/* pipeline stack functions */ +SOKOL_GL_API_DECL void sgl_load_default_pipeline(void); +SOKOL_GL_API_DECL void sgl_load_pipeline(sgl_pipeline pip); +SOKOL_GL_API_DECL void sgl_push_pipeline(void); +SOKOL_GL_API_DECL void sgl_pop_pipeline(void); + +/* matrix stack functions */ +SOKOL_GL_API_DECL void sgl_matrix_mode_modelview(void); +SOKOL_GL_API_DECL void sgl_matrix_mode_projection(void); +SOKOL_GL_API_DECL void sgl_matrix_mode_texture(void); +SOKOL_GL_API_DECL void sgl_load_identity(void); +SOKOL_GL_API_DECL void sgl_load_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_load_transpose_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_mult_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_mult_transpose_matrix(const float m[16]); +SOKOL_GL_API_DECL void sgl_rotate(float angle_rad, float x, float y, float z); +SOKOL_GL_API_DECL void sgl_scale(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_translate(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_frustum(float l, float r, float b, float t, float n, float f); +SOKOL_GL_API_DECL void sgl_ortho(float l, float r, float b, float t, float n, float f); +SOKOL_GL_API_DECL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far); +SOKOL_GL_API_DECL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z); +SOKOL_GL_API_DECL void sgl_push_matrix(void); +SOKOL_GL_API_DECL void sgl_pop_matrix(void); + +/* these functions only set the internal 'current texcoord / color / point size' (valid inside or outside begin/end) */ +SOKOL_GL_API_DECL void sgl_t2f(float u, float v); +SOKOL_GL_API_DECL void sgl_c3f(float r, float g, float b); +SOKOL_GL_API_DECL void sgl_c4f(float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_c1i(uint32_t rgba); +SOKOL_GL_API_DECL void sgl_point_size(float s); + +/* define primitives, each begin/end is one draw command */ +SOKOL_GL_API_DECL void sgl_begin_points(void); +SOKOL_GL_API_DECL void sgl_begin_lines(void); +SOKOL_GL_API_DECL void sgl_begin_line_strip(void); +SOKOL_GL_API_DECL void sgl_begin_triangles(void); +SOKOL_GL_API_DECL void sgl_begin_triangle_strip(void); +SOKOL_GL_API_DECL void sgl_begin_quads(void); +SOKOL_GL_API_DECL void sgl_v2f(float x, float y); +SOKOL_GL_API_DECL void sgl_v3f(float x, float y, float z); +SOKOL_GL_API_DECL void sgl_v2f_t2f(float x, float y, float u, float v); +SOKOL_GL_API_DECL void sgl_v3f_t2f(float x, float y, float z, float u, float v); +SOKOL_GL_API_DECL void sgl_v2f_c3f(float x, float y, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v2f_c1i(float x, float y, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_GL_API_DECL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba); +SOKOL_GL_API_DECL void sgl_end(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void sgl_setup(const sgl_desc_t& desc) { return sgl_setup(&desc); } +inline sgl_context sgl_make_context(const sgl_context_desc_t& desc) { return sgl_make_context(&desc); } +inline sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc& desc) { return sgl_make_pipeline(&desc); } +inline sgl_pipeline sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc& desc) { return sgl_context_make_pipeline(ctx, &desc); } +#endif +#endif /* SOKOL_GL_INCLUDED */ + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_GL_IMPL +#define SOKOL_GL_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sgl_desc_t.allocator to override memory allocation functions" +#endif + +#include // malloc/free +#include // memset +#include // M_PI, sqrtf, sinf, cosf + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#define _sgl_def(val, def) (((val) == 0) ? (def) : (val)) +#define _SGL_INIT_COOKIE (0xABCDABCD) + +/* + Embedded source code compiled with: + + sokol-shdc -i sgl.glsl -o sgl.h -l glsl410:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + layout(binding=0) uniform vs_params { + mat4 mvp; + mat4 tm; + }; + in vec4 position; + in vec2 texcoord0; + in vec4 color0; + in float psize; + out vec4 uv; + out vec4 color; + void main() { + gl_Position = mvp * position; + #ifndef SOKOL_WGSL + gl_PointSize = psize; + #endif + uv = tm * vec4(texcoord0, 0.0, 1.0); + color = color0; + } + @end + + @fs fs + layout(binding=0) uniform texture2D tex; + layout(binding=0) uniform sampler smp; + in vec4 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = texture(sampler2D(tex, smp), uv.xy) * color; + } + @end + + @program sgl vs fs +*/ + +#if defined(SOKOL_GLCORE) +/* + #version 410 + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + layout(location = 0) out vec4 uv; + layout(location = 1) in vec2 texcoord0; + layout(location = 1) out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sgl_vs_source_glsl410[520] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, + 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, + 0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f, + 0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76, + 0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x31,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53, + 0x69,0x7a,0x65,0x20,0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x75,0x76,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37, + 0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f, + 0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + #version 410 + + uniform sampler2D tex_smp; + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 uv; + layout(location = 1) in vec4 color; + + void main() + { + frag_color = texture(tex_smp, uv.xy) * color; + } +*/ +static const uint8_t _sgl_fs_source_glsl410[222] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x31,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f, + 0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74, + 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29, + 0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES3) +/* + #version 300 es + + uniform vec4 vs_params[8]; + layout(location = 0) in vec4 position; + layout(location = 3) in float psize; + out vec4 uv; + layout(location = 1) in vec2 texcoord0; + out vec4 color; + layout(location = 2) in vec4 color0; + + void main() + { + gl_Position = mat4(vs_params[0], vs_params[1], vs_params[2], vs_params[3]) * position; + gl_PointSize = psize; + uv = mat4(vs_params[4], vs_params[5], vs_params[6], vs_params[7]) * vec4(texcoord0, 0.0, 1.0); + color = color0; + } +*/ +static const uint8_t _sgl_vs_source_glsl300es[481] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74, + 0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b, + 0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20, + 0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x70, + 0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d, + 0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65, + 0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +/* + #version 300 es + precision mediump float; + precision highp int; + + uniform highp sampler2D tex_smp; + + layout(location = 0) out highp vec4 frag_color; + in highp vec4 uv; + in highp vec4 color; + + void main() + { + frag_color = texture(tex_smp, uv.xy) * color; + } +*/ +static const uint8_t _sgl_fs_source_glsl300es[253] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75, + 0x76,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29,0x20, + 0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sgl_vs_bytecode_metal_macos[3381] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x35,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x59,0xf1,0xe4,0x22,0x0f,0x75,0x42, + 0x53,0x9c,0x2a,0x05,0xe4,0xbd,0x91,0x28,0x82,0x06,0x3f,0x0d,0xb9,0xef,0x34,0xbd, + 0xe8,0xfa,0x46,0x5c,0x66,0x1f,0xc6,0xde,0x3a,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x04,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xfe,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x68,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x18,0x42,0x01,0x2c,0x40, + 0x05,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79, + 0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5, + 0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07, + 0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0, + 0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07, + 0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, + 0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, + 0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60, + 0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f, + 0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79, + 0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, + 0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07, + 0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, + 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50, + 0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07, + 0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, + 0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07, + 0x72,0x30,0x84,0x49,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8,0x02,0x01,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, + 0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50, + 0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00,0x88,0x8d,0x25,0x3c,0x00,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x01,0x01,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2, + 0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50, + 0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c, + 0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65, + 0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54, + 0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7, + 0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7, + 0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e, + 0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74, + 0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x61,0x19,0x84,0xa5,0xc9, + 0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5, + 0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89, + 0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19, + 0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7, + 0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70, + 0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5, + 0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28, + 0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71,0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b, + 0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6,0x26,0x37,0x84,0x51,0x1e,0xc5,0x52, + 0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26,0xe7,0x02,0xf7,0x36,0x97,0x46,0x97, + 0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88, + 0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74,0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2, + 0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98,0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63, + 0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3,0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29, + 0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c,0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c, + 0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a,0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34, + 0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37,0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c, + 0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65,0x64,0x28,0x5f,0x5f,0x61,0x69,0x72, + 0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c,0x64,0x65,0x72,0x5f,0x5f,0x29,0x44, + 0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86,0x50,0x8b,0xa0,0x84,0x81,0x22,0x06, + 0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29,0x93,0x42,0x06,0x34,0xcc,0xd8,0xde, + 0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd,0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96, + 0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94,0x31,0x50,0x22,0xc5,0x0c,0x94,0x49, + 0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0xc2,0x40,0x11, + 0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91,0x94,0x49,0x49,0x03,0x16,0x70,0x73, + 0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40,0x11,0x83,0xc5,0x58,0x02,0x65,0x0c, + 0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61,0x69,0x72,0x2e,0x62,0x75,0x66,0x66, + 0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea,0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2, + 0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95,0x85,0x91,0x91,0x0a,0x4b,0x93,0x73, + 0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb,0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b, + 0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47,0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d, + 0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38,0x50,0xe4,0x60,0x21,0x16,0x62,0x11, + 0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c,0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e, + 0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f, + 0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33,0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1, + 0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62,0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02, + 0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d,0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c, + 0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40,0x51,0x03,0x85,0x0d,0x94,0x3c,0x18, + 0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79,0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b, + 0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33,0x95,0xd6,0x06,0xc7,0x56,0x06,0x32, + 0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44,0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81, + 0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9,0x83,0x46,0x19,0x11,0xb1,0x03,0x3b, + 0xd8,0x43,0x3b,0xb8,0x41,0x3b,0xbc,0x03,0x39,0xd4,0x03,0x3b,0x94,0x83,0x1b,0x98, + 0x03,0x3b,0x84,0xc3,0x39,0xcc,0xc3,0x14,0x21,0x18,0x46,0x28,0xec,0xc0,0x0e,0xf6, + 0xd0,0x0e,0x6e,0x90,0x0e,0xe4,0x50,0x0e,0xee,0x40,0x0f,0x53,0x82,0x62,0xc4,0x12, + 0x0e,0xe9,0x20,0x0f,0x6e,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e, + 0x53,0x02,0x63,0x04,0x15,0x0e,0xe9,0x20,0x0f,0x6e,0xc0,0x0e,0xe1,0xe0,0x0e,0xe7, + 0x50,0x0f,0xe1,0x70,0x0e,0xe5,0xf0,0x0b,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0, + 0x0e,0xee,0x30,0x25,0x40,0x46,0x4c,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe3,0xf0,0x0e, + 0xed,0x00,0x0f,0xe9,0xc0,0x0e,0xe5,0xf0,0x0b,0xef,0x00,0x0f,0xf4,0x90,0x0e,0xef, + 0xe0,0x0e,0xf3,0x30,0x65,0x50,0x18,0x67,0x84,0x12,0x0e,0xe9,0x20,0x0f,0x6e,0x60, + 0x0f,0xe5,0x20,0x0f,0xf4,0x50,0x0e,0xf8,0x30,0x25,0xd8,0x03,0x00,0x79,0x18,0x00, + 0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d, + 0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c, + 0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d, + 0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d, + 0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79, + 0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc, + 0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50, + 0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30, + 0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03, + 0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07, + 0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76, + 0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98, + 0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8, + 0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21, + 0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43, + 0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f, + 0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70, + 0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0, + 0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40, + 0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41, + 0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e, + 0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07, + 0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f, + 0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d, + 0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38, + 0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88, + 0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08, + 0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50, + 0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01, + 0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43, + 0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4, + 0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e, + 0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6, + 0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0, + 0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1, + 0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f, + 0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc, + 0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19, + 0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3, + 0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87, + 0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77, + 0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00, + 0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x3e,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22, + 0x86,0x61,0x18,0xc6,0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8, + 0x95,0x40,0x19,0x14,0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00, + 0x00,0xe3,0x15,0x8c,0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9, + 0x8c,0x57,0x40,0x96,0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63, + 0x41,0x01,0x9f,0xf1,0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53, + 0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14, + 0x94,0x41,0x06,0x30,0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b, + 0x03,0x37,0x68,0x83,0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67, + 0xbc,0x62,0x0c,0xd2,0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06, + 0x26,0x04,0xf2,0xb1,0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18, + 0x50,0x50,0x6c,0x08,0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30, + 0xdb,0x10,0x3c,0xc2,0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x09,0x00,0x00, + 0x00,0x5b,0x86,0x20,0x00,0x85,0x2d,0x43,0x11,0x80,0xc2,0x96,0x41,0x09,0x40,0x61, + 0xcb,0xf0,0x04,0xa0,0xb0,0x65,0xa0,0x02,0x50,0xd8,0x32,0x60,0x01,0x28,0x6c,0x19, + 0xba,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv.xy) * in.color; + return out; + } +*/ +static const uint8_t _sgl_fs_bytecode_metal_macos[3033] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xd9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x43,0x4f,0xc0,0x79,0x15,0x0f,0x7e, + 0x56,0x86,0x83,0xab,0x09,0x97,0xeb,0xad,0x1f,0xad,0xc5,0x99,0xa0,0x69,0x5d,0x31, + 0x4f,0x5e,0x5b,0x06,0x1b,0x6c,0x91,0x10,0xa7,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xe4,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xb6,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1d,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x48,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x08,0x00,0x00,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0, + 0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87, + 0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38, + 0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0, + 0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07, + 0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a, + 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60, + 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0, + 0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07, + 0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, + 0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, + 0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0, + 0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07, + 0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76, + 0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50, + 0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07, + 0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74, + 0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60, + 0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00, + 0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64,0x81,0x00,0x00,0x00, + 0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26, + 0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70, + 0x2c,0xe1,0x01,0x00,0x00,0x79,0x18,0x00,0x00,0xd1,0x00,0x00,0x00,0x1a,0x03,0x4c, + 0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42, + 0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62, + 0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6, + 0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06, + 0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c, + 0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0, + 0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6, + 0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72, + 0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74, + 0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x61, + 0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85, + 0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91, + 0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c, + 0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39, + 0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2, + 0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e, + 0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86,0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce, + 0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b, + 0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97, + 0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69,0x72,0x2e,0x70,0x65,0x72,0x73,0x70, + 0x65,0x63,0x74,0x69,0x76,0x65,0x14,0xea,0xec,0x86,0x48,0xcb,0xf0,0x58,0xcf,0xf5, + 0x60,0x4f,0xf6,0x40,0x4f,0xf4,0x48,0x8f,0xc6,0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed, + 0x6d,0xcc,0x2d,0x26,0x85,0xc5,0xd8,0x1b,0xdb,0x9b,0xdc,0x10,0x69,0x11,0x1e,0xeb, + 0xe1,0x1e,0xec,0xc9,0x1e,0xe8,0x89,0x1e,0xe9,0xe9,0xb8,0x84,0xa5,0xc9,0xb9,0xd0, + 0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b,0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3, + 0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3,0xa3,0x12,0x96,0x26,0xe7,0x32,0x17, + 0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f,0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7, + 0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac,0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d, + 0x19,0xde,0x10,0x6a,0x21,0x9e,0xef,0x01,0x83,0x65,0x58,0x84,0x27,0x0c,0x1e,0xe8, + 0x11,0x83,0x47,0x7a,0xc6,0x80,0x4b,0x58,0x9a,0x9c,0xcb,0x5c,0x58,0x1b,0x1c,0x5b, + 0x99,0x1c,0x8f,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x0e,0x73,0x6d,0x70,0x43,0xa4, + 0xe5,0x78,0xca,0xe0,0x01,0x83,0x65,0x58,0x84,0x07,0x7a,0xcc,0xe0,0x91,0x9e,0x33, + 0x18,0x82,0x3c,0xdb,0xe3,0x3d,0x64,0xf0,0xa0,0xc1,0x10,0x03,0x01,0x9e,0xea,0x49, + 0x03,0x5e,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70, + 0x65,0x73,0x28,0x6d,0x61,0x69,0x6e,0x30,0x29,0x43,0x88,0x87,0x0d,0x9e,0x35,0x20, + 0x16,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6,0x06,0x57,0xd6, + 0x42,0x57,0x86,0x47,0x57,0x27,0x57,0x36,0x37,0xc4,0x78,0xdc,0xe0,0x61,0x83,0xa7, + 0x0d,0x88,0x85,0xa5,0xc9,0xb5,0x84,0xb1,0xa5,0x85,0xcd,0xb5,0xcc,0x8d,0xbd,0xc1, + 0x95,0xb5,0xcc,0x85,0xb5,0xc1,0xb1,0x95,0xc9,0xcd,0x0d,0x31,0x1e,0x38,0x78,0xd8, + 0xe0,0x79,0x83,0x21,0xc4,0xe3,0x06,0x0f,0x1c,0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1, + 0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d, + 0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23,0x14,0x76,0x60,0x07,0x7b,0x68,0x07, + 0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87,0x29,0x41,0x31,0x62,0x09,0x87,0x74, + 0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x81, + 0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60,0x87,0x70,0x70,0x87,0x73,0xa8,0x87, + 0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77, + 0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79,0x70,0x83,0x71,0x78,0x87,0x76,0x80, + 0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80,0x07,0x7a,0x48,0x87,0x77,0x70,0x87, + 0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87,0x74,0x90,0x07,0x37,0x30,0x07,0x79, + 0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a,0x98,0x12,0xa8,0x01,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c, + 0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07, + 0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42, + 0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83, + 0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07, + 0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70, + 0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3, + 0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2, + 0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc, + 0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68, + 0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07, + 0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72, + 0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec, + 0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc, + 0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8, + 0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03, + 0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c, + 0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74, + 0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4, + 0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc, + 0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1, + 0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50, + 0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51, + 0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21, + 0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06, + 0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c, + 0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77, + 0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec, + 0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8, + 0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84, + 0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03, + 0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87, + 0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f, + 0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3, + 0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2, + 0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21, + 0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83, + 0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87, + 0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e, + 0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90, + 0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85, + 0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00, + 0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f, + 0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20, + 0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x14,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xc4,0x46,0x00,0xc6,0x12,0x80,0x80, + 0xd4,0x08,0x40,0x0d,0x90,0x98,0x01,0xa0,0x30,0x03,0x40,0x60,0x04,0x00,0x00,0x00, + 0x00,0x83,0x0c,0x8b,0x60,0x8c,0x18,0x28,0x43,0x40,0x29,0x49,0x50,0x20,0x86,0x60, + 0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x08,0x88,0x01,0x00,0x00,0x02,0x00,0x00, + 0x00,0x5b,0x86,0xe0,0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sgl_vs_bytecode_metal_ios[3493] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xa5,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x90,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x81,0x80,0x75,0xbc,0xa9,0xd0,0xb9, + 0x4f,0xee,0x63,0x10,0x8f,0xfe,0x66,0xa5,0x3b,0x40,0xb7,0x43,0x44,0x12,0xba,0x69, + 0x7d,0xf3,0x83,0x9a,0xda,0x47,0x00,0x29,0xec,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x40,0x00,0x00,0x00,0x56,0x41,0x54, + 0x54,0x2a,0x00,0x04,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, + 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, + 0x72,0x30,0x00,0x02,0x80,0x70,0x73,0x69,0x7a,0x65,0x00,0x03,0x80,0x56,0x41,0x54, + 0x59,0x06,0x00,0x04,0x00,0x06,0x04,0x06,0x03,0x45,0x4e,0x44,0x54,0x04,0x00,0x00, + 0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00,0x00,0x14,0x00,0x00, + 0x00,0x70,0x0c,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0x19,0x03,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00, + 0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84, + 0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0b, + 0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44,0x24,0x48,0x0a,0x90, + 0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8,0x48,0x11,0x62,0xa8, + 0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00,0x00,0x70,0x00,0x00, + 0x00,0x1b,0x7e,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00,0x8a,0x08,0x07,0x78, + 0x80,0x07,0x79,0x78,0x07,0x7c,0x68,0x03,0x73,0xa8,0x07,0x77,0x18,0x87,0x36,0x30, + 0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca, + 0x01,0x20,0xda,0x21,0x1d,0xdc,0xa1,0x0d,0xd8,0xa1,0x1c,0xce,0x21,0x1c,0xd8,0xa1, + 0x0d,0xec,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0xe0,0x1e,0xd2,0x81,0x1c, + 0xe8,0x01,0x1d,0x80,0x38,0x90,0x03,0x3c,0x00,0x06,0x77,0x78,0x87,0x36,0x10,0x87, + 0x7a,0x48,0x07,0x76,0xa0,0x87,0x74,0x70,0x87,0x79,0x00,0x08,0x77,0x78,0x87,0x36, + 0x30,0x07,0x79,0x08,0x87,0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0, + 0x87,0x72,0x90,0x87,0x36,0x28,0x07,0x76,0x48,0x87,0x76,0x00,0xe8,0x41,0x1e,0xea, + 0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1, + 0x0d,0xe0,0xe1,0x1d,0xd2,0xc1,0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d, + 0xd2,0xa1,0x1d,0xda,0xc0,0x1d,0xde,0xc1,0x1d,0xda,0x80,0x1d,0xca,0x21,0x1c,0xcc, + 0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77, + 0x30,0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xdc,0x21,0x1c,0xdc, + 0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a, + 0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x79,0x48,0x87,0x73,0x70, + 0x87,0x72,0x20,0x87,0x36,0xd0,0x87,0x72,0x90,0x87,0x77,0x98,0x87,0x36,0x30,0x07, + 0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01, + 0x20,0xdc,0xe1,0x1d,0xda,0x80,0x1e,0xe4,0x21,0x1c,0xe0,0x01,0x1e,0xd2,0xc1,0x1d, + 0xce,0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07, + 0x80,0x98,0x07,0x7a,0x08,0x87,0x71,0x58,0x87,0x36,0x80,0x07,0x79,0x78,0x07,0x7a, + 0x28,0x87,0x71,0xa0,0x87,0x77,0x90,0x87,0x36,0x10,0x87,0x7a,0x30,0x07,0x73,0x28, + 0x07,0x79,0x68,0x83,0x79,0x48,0x07,0x7d,0x28,0x07,0x00,0x0f,0x00,0xa2,0x1e,0xdc, + 0x61,0x1e,0xc2,0xc1,0x1c,0xca,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, + 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x36,0x6c,0x42,0x01,0x2c,0x40, + 0x35,0x84,0x43,0x3a,0xc8,0x43,0x1b,0x88,0x43,0x3d,0x98,0x83,0x39,0x94,0x83,0x3c, + 0xb4,0x81,0x3b,0xbc,0x43,0x1b,0x84,0x03,0x3b,0xa4,0x43,0x38,0xcc,0x03,0x00,0x00, + 0x00,0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00, + 0x00,0x20,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x44,0x33,0x00,0xc3,0x08,0x04,0x60,0x89,0x10,0x02,0x18,0x46,0x10,0x80, + 0x24,0x08,0x33,0x51,0xf3,0x40,0x0f,0xf2,0x50,0x0f,0xe3,0x40,0x0f,0x6e,0xd0,0x0e, + 0xe5,0x40,0x0f,0xe1,0xc0,0x0e,0x7a,0xa0,0x07,0xed,0x10,0x0e,0xf4,0x20,0x0f,0xe9, + 0x80,0x0f,0x28,0x20,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x49,0xff,0x03,0x44,0x00, + 0x23,0x21,0xa1,0x94,0x41,0x04,0x43,0x28,0x86,0x08,0x23,0x80,0x43,0x68,0x20,0x60, + 0x8e,0x00,0x0c,0x52,0x60,0xcd,0x11,0x80,0xc2,0x20,0x42,0x20,0x0c,0x23,0x10,0xcb, + 0x08,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, + 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37, + 0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80, + 0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06, + 0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9, + 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60, + 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0, + 0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73, + 0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40, + 0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78, + 0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10, + 0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07, + 0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6, + 0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60, + 0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07, + 0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71, + 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80, + 0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00, + 0x00,0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00,0x0b,0x00,0x00,0x00,0x32,0x1e,0x98, + 0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02, + 0x50,0x04,0x05,0x18,0x50,0x08,0x65,0x50,0x80,0x02,0x05,0x51,0x20,0xd4,0x46,0x00, + 0x88,0x8d,0x25,0x48,0x00,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x12,0x01,0x00, + 0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37, + 0xb7,0x21,0xc6,0x32,0x28,0x00,0xb3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b, + 0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x46, + 0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65,0x88,0xa0,0x10,0x43, + 0x8c,0x25,0x58,0x90,0x45,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25, + 0x58,0x82,0x45,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6, + 0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36, + 0x44,0x50,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e, + 0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65, + 0x43,0x04,0x65,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95, + 0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1, + 0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a, + 0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97, + 0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e, + 0x61,0x72,0x67,0x5f,0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8, + 0xde,0xc2,0xe8,0x68,0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5, + 0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28,0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71, + 0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0x61,0x31,0xf6,0xc6,0xf6, + 0x26,0x37,0x84,0x51,0x1e,0xc5,0x52,0x22,0x45,0x52,0x26,0xe5,0x22,0x13,0x96,0x26, + 0xe7,0x02,0xf7,0x36,0x97,0x46,0x97,0xf6,0xe6,0xc6,0xe5,0x8c,0xed,0x0b,0xea,0x6d, + 0x2e,0x8d,0x2e,0xed,0xcd,0x6d,0x88,0xa2,0x64,0x4a,0xa4,0x48,0xca,0xa4,0x68,0x74, + 0xc2,0xd2,0xe4,0x5c,0xe0,0xde,0xd2,0xdc,0xe8,0xbe,0xe6,0xd2,0xf4,0xca,0x58,0x98, + 0xb1,0xbd,0x85,0xd1,0x91,0x39,0x63,0xfb,0x82,0x7a,0x4b,0x73,0xa3,0x9b,0x4a,0xd3, + 0x2b,0x1b,0xa2,0x28,0x9c,0x12,0x29,0x9d,0x32,0x29,0xde,0x10,0x44,0xa9,0x14,0x4c, + 0xd9,0x94,0x8f,0x50,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19,0xde,0x57,0x9a, + 0x1b,0x5c,0x1d,0x1d,0xa5,0xb0,0x34,0x39,0x17,0xb6,0xb7,0xb1,0x30,0xba,0xb4,0x37, + 0xb7,0xaf,0x34,0x37,0xb2,0x32,0x3c,0x7a,0x67,0x65,0x6e,0x65,0x72,0x61,0x74,0x65, + 0x64,0x28,0x5f,0x5f,0x61,0x69,0x72,0x5f,0x70,0x6c,0x61,0x63,0x65,0x68,0x6f,0x6c, + 0x64,0x65,0x72,0x5f,0x5f,0x29,0x44,0xe0,0xde,0xe6,0xd2,0xe8,0xd2,0xde,0xdc,0x86, + 0x50,0x8b,0xa0,0x84,0x81,0x22,0x06,0x8b,0xb0,0x04,0xca,0x18,0x28,0x91,0x22,0x29, + 0x93,0x42,0x06,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0x98,0xd0,0x95,0xe1,0x8d,0xbd, + 0xbd,0xc9,0x91,0xc1,0x0c,0xa1,0x96,0x40,0x09,0x03,0x45,0x0c,0x96,0x60,0x09,0x94, + 0x31,0x50,0x22,0xc5,0x0c,0x94,0x49,0x39,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x43,0xa8,0x65,0x50,0xc2,0x40,0x11,0x83,0x65,0x58,0x02,0x65,0x0c,0x94,0x48,0x91, + 0x94,0x49,0x49,0x03,0x16,0x70,0x73,0x69,0x7a,0x65,0x43,0xa8,0xc5,0x50,0xc2,0x40, + 0x11,0x83,0xc5,0x58,0x02,0x65,0x0c,0x94,0x48,0xe9,0x94,0x49,0x59,0x03,0x2a,0x61, + 0x69,0x72,0x2e,0x62,0x75,0x66,0x66,0x65,0x72,0x7c,0xc2,0xd2,0xe4,0x5c,0xc4,0xea, + 0xcc,0xcc,0xca,0xe4,0xbe,0xe6,0xd2,0xf4,0xca,0x88,0x84,0xa5,0xc9,0xb9,0xc8,0x95, + 0x85,0x91,0x91,0x0a,0x4b,0x93,0x73,0x99,0xa3,0x93,0xab,0x1b,0xa3,0xfb,0xa2,0xcb, + 0x83,0x2b,0xfb,0x4a,0x73,0x33,0x7b,0x23,0x62,0xc6,0xf6,0x16,0x46,0x47,0x83,0x47, + 0xc3,0xa1,0xcd,0x0e,0x8e,0x02,0x5d,0xdb,0x10,0x6a,0x11,0x16,0x62,0x11,0x94,0x38, + 0x50,0xe4,0x60,0x21,0x16,0x62,0x11,0x94,0x38,0x50,0xe6,0x80,0x51,0x58,0x9a,0x9c, + 0x4b,0x98,0xdc,0xd9,0x17,0x5d,0x1e,0x5c,0xd9,0xd7,0x5c,0x9a,0x5e,0x19,0xaf,0xb0, + 0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x30,0xb6,0xb4,0x33, + 0xb7,0xaf,0xb9,0x34,0xbd,0x32,0x26,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73, + 0x1c,0xbe,0x62,0x72,0x86,0x90,0xc1,0x52,0x28,0x6d,0xa0,0xb8,0xc1,0x72,0x28,0x62, + 0xb0,0x08,0x4b,0xa0,0xbc,0x81,0x02,0x07,0x0a,0x1d,0x28,0x75,0xb0,0x1c,0x8a,0x1d, + 0x2c,0x89,0x12,0x29,0x77,0xa0,0x4c,0x0a,0x1e,0x0c,0x51,0x94,0x32,0x50,0xd0,0x40, + 0x51,0x03,0x85,0x0d,0x94,0x3c,0x18,0x62,0x24,0x80,0x02,0x06,0x8a,0x1e,0xf0,0x79, + 0x6b,0x73,0x4b,0x83,0x7b,0xa3,0x2b,0x73,0xa3,0x03,0x19,0x43,0x0b,0x93,0xe3,0x33, + 0x95,0xd6,0x06,0xc7,0x56,0x06,0x32,0xb4,0xb2,0x02,0x42,0x25,0x14,0x14,0x34,0x44, + 0x50,0xfa,0x60,0x88,0xa1,0xf0,0x81,0xe2,0x07,0x8d,0x32,0xc4,0x50,0xfe,0x40,0xf9, + 0x83,0x46,0xe1,0x15,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36,0xd7,0x32,0x37,0xf6, + 0x06,0x57,0x36,0x87,0xd2,0x16,0x96,0xe6,0x06,0x93,0x32,0x84,0x50,0x44,0x41,0x09, + 0x05,0x5a,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69,0x61,0x73,0x2d,0x73,0x63,0x6f,0x70, + 0x65,0x2d,0x61,0x72,0x67,0x28,0x34,0x29,0x43,0x0c,0x85,0x14,0x14,0x51,0x50,0x46, + 0x61,0x88,0xa0,0x90,0xc2,0x88,0x88,0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xda,0xe1, + 0x1d,0xc8,0xa1,0x1e,0xd8,0xa1,0x1c,0xdc,0xc0,0x1c,0xd8,0x21,0x1c,0xce,0x61,0x1e, + 0xa6,0x08,0xc1,0x30,0x42,0x61,0x07,0x76,0xb0,0x87,0x76,0x70,0x83,0x74,0x20,0x87, + 0x72,0x70,0x07,0x7a,0x98,0x12,0x14,0x23,0x96,0x70,0x48,0x07,0x79,0x70,0x03,0x7b, + 0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x18,0x23,0xa8,0x70,0x48, + 0x07,0x79,0x70,0x03,0x76,0x08,0x07,0x77,0x38,0x87,0x7a,0x08,0x87,0x73,0x28,0x87, + 0x5f,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x01,0x32,0x62, + 0x0a,0x87,0x74,0x90,0x07,0x37,0x18,0x87,0x77,0x68,0x07,0x78,0x48,0x07,0x76,0x28, + 0x87,0x5f,0x78,0x07,0x78,0xa0,0x87,0x74,0x78,0x07,0x77,0x98,0x87,0x29,0x83,0xc2, + 0x38,0x23,0x94,0x70,0x48,0x07,0x79,0x70,0x03,0x7b,0x28,0x07,0x79,0xa0,0x87,0x72, + 0xc0,0x87,0x29,0xc1,0x1e,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00, + 0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84, + 0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed, + 0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66, + 0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c, + 0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70, + 0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90, + 0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4, + 0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83, + 0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14, + 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70, + 0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80, + 0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0, + 0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1, + 0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d, + 0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39, + 0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc, + 0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70, + 0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53, + 0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e, + 0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c, + 0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38, + 0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37, + 0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33, + 0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4, + 0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0, + 0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3, + 0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07, + 0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23, + 0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c, + 0xb8,0xc1,0x39,0xc8,0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76, + 0x60,0x07,0x71,0x08,0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xf0,0x20,0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10, + 0x0e,0xe3,0x30,0x0e,0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e, + 0xf8,0x30,0x23,0xe2,0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d, + 0xe6,0x21,0x1d,0xc4,0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b, + 0xbc,0x43,0x3d,0xb8,0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77, + 0x78,0x07,0x7a,0x08,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5, + 0x10,0x0e,0xf0,0x10,0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50, + 0x0e,0x33,0x28,0x30,0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71, + 0xa0,0x87,0x74,0x78,0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98, + 0x07,0x00,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30, + 0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x42,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xf4,0xc6,0x22,0x86,0x61,0x18,0xc6, + 0x22,0x04,0x41,0x10,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8,0x95,0x40,0x19,0x14, + 0x01,0x8d,0x19,0x00,0x12,0x33,0x00,0x14,0x66,0x00,0x66,0x00,0x00,0xe3,0x15,0x8c, + 0xa4,0x69,0x12,0x05,0x65,0x90,0x41,0x22,0x14,0x13,0x02,0xf9,0x8c,0x57,0x40,0x96, + 0xe7,0x2d,0x14,0x94,0x41,0x06,0xeb,0x78,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1, + 0x8a,0x6a,0x1b,0x83,0x31,0x70,0x28,0x28,0x83,0x0c,0x1b,0x53,0x99,0x10,0xc8,0xc7, + 0x8a,0x00,0x3e,0xe3,0x15,0x1a,0x18,0xa0,0x01,0x1a,0x50,0x14,0x94,0x41,0x06,0x30, + 0x88,0x36,0x13,0x02,0xf9,0x58,0x11,0xc0,0x67,0xbc,0xe2,0x2b,0x03,0x37,0x68,0x83, + 0x32,0xa0,0xa0,0x0c,0x32,0x90,0x41,0xd6,0x99,0x10,0xc8,0x67,0xbc,0x62,0x0c,0xd2, + 0x40,0x0e,0xe2,0xc0,0xa3,0xa0,0x0c,0x32,0xa0,0x41,0x27,0x06,0x26,0x04,0xf2,0xb1, + 0xa0,0x80,0xcf,0x78,0x05,0x1a,0xb8,0xc1,0x1d,0xd8,0x81,0x18,0x50,0x50,0x6c,0x08, + 0xe0,0x33,0xdb,0x20,0x06,0x01,0x30,0xdb,0x10,0xb8,0x41,0x30,0xdb,0x10,0x3c,0xc2, + 0x6c,0x43,0xf0,0x06,0x43,0x06,0x01,0x31,0x00,0x0d,0x00,0x00,0x00,0x5b,0x8a,0x20, + 0x00,0x85,0xa3,0x14,0xb6,0x14,0x45,0x00,0x0a,0x47,0x29,0x6c,0x29,0x94,0x00,0x14, + 0x8e,0x52,0xd8,0x52,0x3c,0x01,0x28,0x1c,0xa5,0xb0,0xa5,0xa0,0x02,0x50,0x38,0x4a, + 0x61,0x4b,0x81,0x05,0xa0,0x70,0x94,0xc2,0x96,0xa2,0x0b,0x40,0xe1,0x28,0x05,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv.xy) * in.color; + return out; + } +*/ +static const uint8_t _sgl_fs_bytecode_metal_ios[3017] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc9,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, + 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, + 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x40,0xd6,0xeb,0xe8,0x54,0x51,0x57, + 0x09,0x04,0x8e,0xcb,0x4c,0x44,0x2c,0x92,0x69,0x71,0x54,0xbd,0xb6,0xe0,0x7e,0x6b, + 0x97,0x01,0x63,0xb9,0x24,0x88,0x76,0x15,0x8c,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, + 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, + 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, + 0x00,0x14,0x00,0x00,0x00,0xd8,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xb3,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, + 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, + 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, + 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, + 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, + 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, + 0x00,0x74,0x00,0x00,0x00,0x1b,0xc2,0x24,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x70,0x80,0x07,0x78,0x90,0x87,0x77,0xc0,0x87,0x36,0x30,0x87,0x7a, + 0x70,0x87,0x71,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00, + 0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0xa2,0x1d,0xd2,0xc1,0x1d,0xda,0x80,0x1d,0xca, + 0xe1,0x1c,0xc2,0x81,0x1d,0xda,0xc0,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d,0xe4,0xa1, + 0x0d,0xee,0x21,0x1d,0xc8,0x81,0x1e,0xd0,0x01,0x88,0x03,0x39,0xc0,0x03,0x60,0x70, + 0x87,0x77,0x68,0x03,0x71,0xa8,0x87,0x74,0x60,0x07,0x7a,0x48,0x07,0x77,0x98,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87,0x72,0x68,0x03,0x78, + 0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72,0x60,0x87,0x74,0x68, + 0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0xc0,0x1c,0xe4, + 0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21,0x1d,0xdc,0x81,0x1e,0xca,0x41, + 0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0xa1,0x0d,0xdc,0xe1,0x1d,0xdc,0xa1,0x0d, + 0xd8,0xa1,0x1c,0xc2,0xc1,0x1c,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87, + 0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d, + 0xda,0xc0,0x1d,0xc2,0xc1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2, + 0x81,0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x98,0x87,0x74,0x38,0x07,0x77,0x28,0x07,0x72,0x68,0x03,0x7d,0x28,0x07,0x79,0x78, + 0x87,0x79,0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8, + 0x41,0x1e,0xea,0xa1,0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe8,0x41,0x1e,0xc2,0x01, + 0x1e,0xe0,0x21,0x1d,0xdc,0xe1,0x1c,0xda,0xa0,0x1d,0xc2,0x81,0x1e,0xd0,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x88,0x79,0xa0,0x87,0x70,0x18,0x87,0x75,0x68,0x03, + 0x78,0x90,0x87,0x77,0xa0,0x87,0x72,0x18,0x07,0x7a,0x78,0x07,0x79,0x68,0x03,0x71, + 0xa8,0x07,0x73,0x30,0x87,0x72,0x90,0x87,0x36,0x98,0x87,0x74,0xd0,0x87,0x72,0x00, + 0xf0,0x00,0x20,0xea,0xc1,0x1d,0xe6,0x21,0x1c,0xcc,0xa1,0x1c,0xda,0xc0,0x1c,0xe0, + 0xa1,0x0d,0xda,0x21,0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x60, + 0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda,0x80,0x10,0xff, + 0xff,0xff,0xff,0x3f,0x00,0x0c,0x20,0x01,0xd5,0x06,0xa3,0x08,0x80,0x05,0xa8,0x36, + 0x18,0x86,0x00,0x2c,0x40,0xb5,0x01,0x39,0xfe,0xff,0xff,0xff,0x7f,0x00,0x18,0x40, + 0x02,0x2a,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x04,0x00,0x00,0x00,0x13,0x86,0x40, + 0x18,0x26,0x0c,0x44,0x61,0x4c,0x18,0x8e,0xc2,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1d,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x48,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x08,0x00,0x00,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0, + 0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87, + 0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9, + 0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0, + 0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07, + 0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72, + 0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0, + 0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, + 0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20, + 0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78, + 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07, + 0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72, + 0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, + 0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07, + 0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, + 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10, + 0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07, + 0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74, + 0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x18, + 0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03,0x04,0x80,0x00,0x00, + 0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98, + 0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02, + 0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0x41,0x02,0x00,0x00,0x79,0x18,0x00, + 0x00,0xd0,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32, + 0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x42,0x3c,0x00,0x84,0x50,0xb9,0x1b,0x43,0x0b, + 0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0xc2,0x23,0x2c,0x05,0xe3,0x20,0x08, + 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, + 0xcd,0x0d,0x64,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x06,0x04,0xa5,0xad,0x8c, + 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x46,0x06,0x46,0x66,0xc6,0x65,0x66,0xa6,0x26,0x65, + 0x88,0xf0,0x10,0x43,0x8c,0x45,0x58,0x8c,0x65,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36, + 0x04,0x79,0x8e,0x45,0x58,0x84,0x65,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06, + 0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17, + 0x26,0xc6,0x56,0x36,0x44,0x78,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70, + 0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e, + 0x61,0x62,0x6c,0x65,0x43,0x84,0x67,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5, + 0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9, + 0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0x9e, + 0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc, + 0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2, + 0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4, + 0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d, + 0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0x9e,0x67,0x19,0x1e,0xe8,0x89,0x1e,0xe9,0x99,0x86, + 0x08,0x0f,0x45,0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee, + 0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b, + 0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69, + 0x72,0x2e,0x70,0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x14,0xea,0xec, + 0x86,0x48,0xcb,0xf0,0x58,0xcf,0xf5,0x60,0x4f,0xf6,0x40,0x4f,0xf4,0x48,0x8f,0xc6, + 0xa5,0x6e,0xae,0x4c,0x0e,0x85,0xed,0x6d,0xcc,0x2d,0x26,0x85,0xc5,0xd8,0x1b,0xdb, + 0x9b,0xdc,0x10,0x69,0x11,0x1e,0xeb,0xe1,0x1e,0xec,0xc9,0x1e,0xe8,0x89,0x1e,0xe9, + 0xe9,0xb8,0x84,0xa5,0xc9,0xb9,0xd0,0x95,0xe1,0xd1,0xd5,0xc9,0x95,0x51,0x0a,0x4b, + 0x93,0x73,0x61,0x7b,0x1b,0x0b,0xa3,0x4b,0x7b,0x73,0xfb,0x4a,0x73,0x23,0x2b,0xc3, + 0xa3,0x12,0x96,0x26,0xe7,0x32,0x17,0xd6,0x06,0xc7,0x56,0x46,0x8c,0xae,0x0c,0x8f, + 0xae,0x4e,0xae,0x4c,0x86,0x8c,0xc7,0x8c,0xed,0x2d,0x8c,0x8e,0x05,0x64,0x2e,0xac, + 0x0d,0x8e,0xad,0xcc,0x87,0x03,0x5d,0x19,0xde,0x10,0x6a,0x21,0x9e,0xef,0x01,0x83, + 0x65,0x58,0x84,0x27,0x0c,0x1e,0xe8,0x11,0x83,0x47,0x7a,0xc6,0x80,0x4b,0x58,0x9a, + 0x9c,0xcb,0x5c,0x58,0x1b,0x1c,0x5b,0x99,0x1c,0x8f,0xb9,0xb0,0x36,0x38,0xb6,0x32, + 0x39,0x0e,0x73,0x6d,0x70,0x43,0xa4,0xe5,0x78,0xca,0xe0,0x01,0x83,0x65,0x58,0x84, + 0x07,0x7a,0xcc,0xe0,0x91,0x9e,0x33,0x18,0x82,0x3c,0xdb,0xe3,0x3d,0x64,0xf0,0xa0, + 0xc1,0x10,0x03,0x01,0x9e,0xea,0x49,0x03,0x5e,0x61,0x69,0x72,0x2d,0x61,0x6c,0x69, + 0x61,0x73,0x2d,0x73,0x63,0x6f,0x70,0x65,0x73,0x28,0x6d,0x61,0x69,0x6e,0x30,0x29, + 0x43,0x88,0x87,0x0d,0x9e,0x35,0x20,0x16,0x96,0x26,0xd7,0x12,0xc6,0x96,0x16,0x36, + 0xd7,0x32,0x37,0xf6,0x06,0x57,0xd6,0x42,0x57,0x86,0x47,0x57,0x27,0x57,0x36,0x37, + 0xc4,0x78,0xdc,0xe0,0x61,0x83,0xa7,0x0d,0x88,0x85,0xa5,0xc9,0xb5,0x84,0xb1,0xa5, + 0x85,0xcd,0xb5,0xcc,0x8d,0xbd,0xc1,0x95,0xb5,0xcc,0x85,0xb5,0xc1,0xb1,0x95,0xc9, + 0xcd,0x0d,0x31,0x1e,0x38,0x78,0xd8,0xe0,0x79,0x83,0x21,0xc4,0xe3,0x06,0x0f,0x1c, + 0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81, + 0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23, + 0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87, + 0x29,0x41,0x31,0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79, + 0x48,0x87,0x77,0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60, + 0x87,0x70,0x70,0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07, + 0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79, + 0x70,0x83,0x71,0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80, + 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87, + 0x74,0x90,0x07,0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a, + 0x98,0x12,0xa8,0x01,0x00,0x79,0x18,0x00,0x00,0xa5,0x00,0x00,0x00,0x33,0x08,0x80, + 0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80, + 0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80, + 0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88, + 0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78, + 0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03, + 0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f, + 0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c, + 0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39, + 0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b, + 0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60, + 0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87, + 0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e, + 0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c, + 0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6, + 0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94, + 0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03, + 0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07, + 0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f, + 0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33, + 0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc, + 0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c, + 0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78, + 0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca, + 0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21, + 0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43, + 0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87, + 0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c, + 0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e, + 0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x48,0x19,0x3b,0xb0,0x83,0x3d, + 0xb4,0x83,0x1b,0x84,0xc3,0x38,0x8c,0x43,0x39,0xcc,0xc3,0x3c,0xb8,0xc1,0x39,0xc8, + 0xc3,0x3b,0xd4,0x03,0x3c,0xcc,0x48,0xb4,0x71,0x08,0x07,0x76,0x60,0x07,0x71,0x08, + 0x87,0x71,0x58,0x87,0x19,0xdb,0xc6,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xf0,0x20, + 0x0f,0xe5,0x30,0x0f,0xe5,0x20,0x0f,0xf6,0x50,0x0e,0x6e,0x10,0x0e,0xe3,0x30,0x0e, + 0xe5,0x30,0x0f,0xf3,0xe0,0x06,0xe9,0xe0,0x0e,0xe4,0x50,0x0e,0xf8,0x30,0x23,0xe2, + 0xec,0x61,0x1c,0xc2,0x81,0x1d,0xd8,0xe1,0x17,0xec,0x21,0x1d,0xe6,0x21,0x1d,0xc4, + 0x21,0x1d,0xd8,0x21,0x1d,0xe8,0x21,0x1f,0x66,0x20,0x9d,0x3b,0xbc,0x43,0x3d,0xb8, + 0x03,0x39,0x94,0x83,0x39,0xcc,0x58,0xbc,0x70,0x70,0x07,0x77,0x78,0x07,0x7a,0x08, + 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x19,0xce,0x87,0x0e,0xe5,0x10,0x0e,0xf0,0x10, + 0x0e,0xec,0xc0,0x0e,0xef,0x30,0x0e,0xf3,0x90,0x0e,0xf4,0x50,0x0e,0x33,0x28,0x30, + 0x08,0x87,0x74,0x90,0x07,0x37,0x30,0x87,0x7a,0x70,0x87,0x71,0xa0,0x87,0x74,0x78, + 0x07,0x77,0xf8,0x85,0x73,0x90,0x87,0x77,0xa8,0x07,0x78,0x98,0x07,0x00,0x00,0x00, + 0x00,0x71,0x20,0x00,0x00,0x08,0x00,0x00,0x00,0x16,0xb0,0x01,0x48,0xe4,0x4b,0x00, + 0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d, + 0x00,0x05,0x03,0x20,0x0d,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x14,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0xc4,0x46,0x00, + 0xc6,0x12,0x80,0x80,0xd4,0x08,0x40,0x0d,0x90,0x98,0x01,0xa0,0x30,0x03,0x40,0x60, + 0x04,0x00,0x00,0x00,0x00,0x83,0x0c,0x8b,0x60,0x8c,0x18,0x28,0x43,0x40,0x29,0x49, + 0x50,0x20,0x86,0x60,0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x08,0x88,0x01,0x00, + 0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0xe0,0x88,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct vs_params + { + float4x4 mvp; + float4x4 tm; + }; + + struct main0_out + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + float4 gl_Position [[position]]; + float gl_PointSize [[point_size]]; + }; + + struct main0_in + { + float4 position [[attribute(0)]]; + float2 texcoord0 [[attribute(1)]]; + float4 color0 [[attribute(2)]]; + float psize [[attribute(3)]]; + }; + + vertex main0_out main0(main0_in in [[stage_in]], constant vs_params& _19 [[buffer(0)]]) + { + main0_out out = {}; + out.gl_Position = _19.mvp * in.position; + out.gl_PointSize = in.psize; + out.uv = _19.tm * float4(in.texcoord0, 0.0, 1.0); + out.color = in.color0; + return out; + } +*/ +static const uint8_t _sgl_vs_source_metal_sim[756] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a, + 0x65,0x20,0x5b,0x5b,0x70,0x6f,0x69,0x6e,0x74,0x5f,0x73,0x69,0x7a,0x65,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75, + 0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72, + 0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a, + 0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69, + 0x7a,0x65,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e, + 0x74,0x6d,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74, + 0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +/* + #include + #include + + using namespace metal; + + struct main0_out + { + float4 frag_color [[color(0)]]; + }; + + struct main0_in + { + float4 uv [[user(locn0)]]; + float4 color [[user(locn1)]]; + }; + + fragment main0_out main0(main0_in in [[stage_in]], texture2d tex [[texture(0)]], sampler smp [[sampler(0)]]) + { + main0_out out = {}; + out.frag_color = tex.sample(smp, in.uv.xy) * in.color; + return out; + } +*/ +static const uint8_t _sgl_fs_source_metal_sim[439] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65, + 0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65, + 0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d,0x70,0x2c,0x20,0x69,0x6e,0x2e, + 0x75,0x76,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75, + 0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +/* + cbuffer vs_params : register(b0) + { + row_major float4x4 _19_mvp : packoffset(c0); + row_major float4x4 _19_tm : packoffset(c4); + }; + + + static float4 gl_Position; + static float gl_PointSize; + static float4 position; + static float psize; + static float4 uv; + static float2 texcoord0; + static float4 color; + static float4 color0; + + struct SPIRV_Cross_Input + { + float4 position : TEXCOORD0; + float2 texcoord0 : TEXCOORD1; + float4 color0 : TEXCOORD2; + float psize : TEXCOORD3; + }; + + struct SPIRV_Cross_Output + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + float4 gl_Position : SV_Position; + }; + + void vert_main() + { + gl_Position = mul(position, _19_mvp); + gl_PointSize = psize; + uv = mul(float4(texcoord0, 0.0f, 1.0f), _19_tm); + color = color0; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + position = stage_input.position; + psize = stage_input.psize; + texcoord0 = stage_input.texcoord0; + color0 = stage_input.color0; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.uv = uv; + stage_output.color = color; + return stage_output; + } +*/ +static const uint8_t _sgl_vs_bytecode_hlsl4[1032] = { + 0x44,0x58,0x42,0x43,0x74,0x7f,0x01,0xd9,0xf4,0xd5,0xed,0x1d,0x74,0xc1,0x30,0x27, + 0xd8,0xe9,0x9d,0x50,0x01,0x00,0x00,0x00,0x08,0x04,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x02,0x00,0x00, + 0x8c,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, + 0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f, + 0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, + 0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, + 0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, + 0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x74,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x03,0x00,0x00,0x68,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, + 0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, + 0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab, + 0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03, + 0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, + 0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04, + 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02, + 0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05, + 0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00, + 0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54, + 0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +/* + Texture2D tex : register(t0); + SamplerState smp : register(s0); + + static float4 frag_color; + static float4 uv; + static float4 color; + + struct SPIRV_Cross_Input + { + float4 uv : TEXCOORD0; + float4 color : TEXCOORD1; + }; + + struct SPIRV_Cross_Output + { + float4 frag_color : SV_Target0; + }; + + void frag_main() + { + frag_color = tex.Sample(smp, uv.xy) * color; + } + + SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) + { + uv = stage_input.uv; + color = stage_input.color; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.frag_color = frag_color; + return stage_output; + } +*/ +static const uint8_t _sgl_fs_bytecode_hlsl4[608] = { + 0x44,0x58,0x42,0x43,0xc8,0x9b,0x66,0x64,0x80,0x2f,0xbe,0x14,0xd9,0x88,0xa0,0x97, + 0x64,0x14,0x66,0xff,0x01,0x00,0x00,0x00,0x60,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x48,0x01,0x00,0x00, + 0xe4,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0x64,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c, + 0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c, + 0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00, + 0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54, + 0x61,0x72,0x67,0x65,0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0x94,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00, + 0x55,0x55,0x00,0x00,0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x62,0x10,0x00,0x03,0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00, + 0x45,0x00,0x00,0x09,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00, + 0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +/* + diagnostic(off, derivative_uniformity); + + struct vs_params { + /_ @offset(0) _/ + mvp : mat4x4f, + /_ @offset(64) _/ + tm : mat4x4f, + } + + @group(0) @binding(0) var x_19 : vs_params; + + var position_1 : vec4f; + + var uv : vec4f; + + var texcoord0 : vec2f; + + var color : vec4f; + + var color0 : vec4f; + + var psize : f32; + + var gl_Position : vec4f; + + fn main_1() { + let x_22 : mat4x4f = x_19.mvp; + let x_25 : vec4f = position_1; + gl_Position = (x_22 * x_25); + let x_32 : mat4x4f = x_19.tm; + let x_36 : vec2f = texcoord0; + uv = (x_32 * vec4f(x_36.x, x_36.y, 0.0f, 1.0f)); + let x_45 : vec4f = color0; + color = x_45; + return; + } + + struct main_out { + @builtin(position) + gl_Position : vec4f, + @location(0) + uv_1 : vec4f, + @location(1) + color_1 : vec4f, + } + + @vertex + fn main(@location(0) position_1_param : vec4f, @location(1) texcoord0_param : vec2f, @location(2) color0_param : vec4f, @location(3) psize_param : f32) -> main_out { + position_1 = position_1_param; + texcoord0 = texcoord0_param; + color0 = color0_param; + psize = psize_param; + main_1(); + return main_out(gl_Position, uv, color); + } +*/ +static const uint8_t _sgl_vs_source_wgsl[1162] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a, + 0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20, + 0x20,0x6d,0x76,0x70,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78,0x34,0x66,0x2c,0x0a, + 0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x36,0x34,0x29, + 0x20,0x2a,0x2f,0x0a,0x20,0x20,0x74,0x6d,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29, + 0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x30,0x29,0x20,0x76,0x61,0x72, + 0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a, + 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x76,0x61,0x72, + 0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76, + 0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65, + 0x3e,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x0a, + 0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x32,0x20,0x3a,0x20,0x6d,0x61, + 0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x35,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f, + 0x31,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x32,0x20,0x2a,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x6d, + 0x61,0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x74,0x6d, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x36,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x3b,0x0a,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x28,0x78,0x5f,0x33,0x32,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x33,0x36,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x33,0x36,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x35,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f,0x34,0x35, + 0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b, + 0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74,0x69,0x6e,0x28,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20,0x75,0x76, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a, + 0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x28,0x31,0x29,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x32,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c, + 0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x33,0x29,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x66,0x33,0x32,0x29, + 0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x20,0x3d,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x70,0x73,0x69,0x7a,0x65,0x20, + 0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x75,0x76,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +/* + diagnostic(off, derivative_uniformity); + + var frag_color : vec4f; + + @group(1) @binding(64) var tex : texture_2d; + + @group(1) @binding(80) var smp : sampler; + + var uv : vec4f; + + var color : vec4f; + + fn main_1() { + let x_23 : vec4f = uv; + let x_25 : vec4f = textureSample(tex, smp, vec2f(x_23.x, x_23.y)); + let x_27 : vec4f = color; + frag_color = (x_25 * x_27); + return; + } + + struct main_out { + @location(0) + frag_color_1 : vec4f, + } + + @fragment + fn main(@location(0) uv_param : vec4f, @location(1) color_param : vec4f) -> main_out { + uv = uv_param; + color = color_param; + main_1(); + return main_out(frag_color); + } +*/ +static const uint8_t _sgl_fs_source_wgsl[647] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75, + 0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x36,0x34, + 0x29,0x20,0x76,0x61,0x72,0x20,0x74,0x65,0x78,0x20,0x3a,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x5f,0x32,0x64,0x3c,0x66,0x33,0x32,0x3e,0x3b,0x0a,0x0a,0x40,0x67, + 0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67, + 0x28,0x38,0x30,0x29,0x20,0x76,0x61,0x72,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x73, + 0x6d,0x70,0x2c,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x32,0x33,0x2e,0x78, + 0x2c,0x20,0x78,0x5f,0x32,0x33,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x32,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x35,0x20,0x2a,0x20,0x78, + 0x5f,0x32,0x37,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f, + 0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x28,0x30,0x29,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40, + 0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20, + 0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x29,0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a, + 0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b, + 0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31, + 0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69, + 0x6e,0x5f,0x6f,0x75,0x74,0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sgl_vs_source_dummy = ""; +static const char* _sgl_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +// ████████ ██ ██ ██████ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ +// ██ ████ ██████ █████ ███████ +// ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ███████ +// +// >>types +typedef enum { + SGL_PRIMITIVETYPE_POINTS = 0, + SGL_PRIMITIVETYPE_LINES, + SGL_PRIMITIVETYPE_LINE_STRIP, + SGL_PRIMITIVETYPE_TRIANGLES, + SGL_PRIMITIVETYPE_TRIANGLE_STRIP, + SGL_PRIMITIVETYPE_QUADS, + SGL_NUM_PRIMITIVE_TYPES, +} _sgl_primitive_type_t; + +typedef struct { + uint32_t id; + sg_resource_state state; +} _sgl_slot_t; + +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _sgl_pool_t; + +typedef struct { + _sgl_slot_t slot; + sg_pipeline pip[SGL_NUM_PRIMITIVE_TYPES]; +} _sgl_pipeline_t; + +typedef struct { + _sgl_pool_t pool; + _sgl_pipeline_t* pips; +} _sgl_pipeline_pool_t; + +typedef enum { + SGL_MATRIXMODE_MODELVIEW, + SGL_MATRIXMODE_PROJECTION, + SGL_MATRIXMODE_TEXTURE, + SGL_NUM_MATRIXMODES +} _sgl_matrix_mode_t; + +typedef struct { + float pos[3]; + float uv[2]; + uint32_t rgba; + float psize; +} _sgl_vertex_t; + +typedef struct { + float v[4][4]; +} _sgl_matrix_t; + +typedef struct { + _sgl_matrix_t mvp; /* model-view-projection matrix */ + _sgl_matrix_t tm; /* texture matrix */ +} _sgl_uniform_t; + +typedef enum { + SGL_COMMAND_DRAW, + SGL_COMMAND_VIEWPORT, + SGL_COMMAND_SCISSOR_RECT, +} _sgl_command_type_t; + +typedef struct { + sg_pipeline pip; + sg_image img; + sg_sampler smp; + int base_vertex; + int num_vertices; + int uniform_index; +} _sgl_draw_args_t; + +typedef struct { + int x, y, w, h; + bool origin_top_left; +} _sgl_viewport_args_t; + +typedef struct { + int x, y, w, h; + bool origin_top_left; +} _sgl_scissor_rect_args_t; + +typedef union { + _sgl_draw_args_t draw; + _sgl_viewport_args_t viewport; + _sgl_scissor_rect_args_t scissor_rect; +} _sgl_args_t; + +typedef struct { + _sgl_command_type_t cmd; + int layer_id; + _sgl_args_t args; +} _sgl_command_t; + +#define _SGL_INVALID_SLOT_INDEX (0) +#define _SGL_MAX_STACK_DEPTH (64) +#define _SGL_DEFAULT_CONTEXT_POOL_SIZE (4) +#define _SGL_DEFAULT_PIPELINE_POOL_SIZE (64) +#define _SGL_DEFAULT_MAX_VERTICES (1<<16) +#define _SGL_DEFAULT_MAX_COMMANDS (1<<14) +#define _SGL_SLOT_SHIFT (16) +#define _SGL_MAX_POOL_SIZE (1<<_SGL_SLOT_SHIFT) +#define _SGL_SLOT_MASK (_SGL_MAX_POOL_SIZE-1) + +typedef struct { + _sgl_slot_t slot; + sgl_context_desc_t desc; + uint32_t frame_id; + uint32_t update_frame_id; + struct { + int cap; + int next; + _sgl_vertex_t* ptr; + } vertices; + struct { + int cap; + int next; + _sgl_uniform_t* ptr; + } uniforms; + struct { + int cap; + int next; + _sgl_command_t* ptr; + } commands; + + /* state tracking */ + int base_vertex; + int quad_vtx_count; /* number of times vtx function has been called, used for non-triangle primitives */ + sgl_error_t error; + bool in_begin; + int layer_id; + float u, v; + uint32_t rgba; + float point_size; + _sgl_primitive_type_t cur_prim_type; + sg_image cur_img; + sg_sampler cur_smp; + bool texturing_enabled; + bool matrix_dirty; /* reset in sgl_end(), set in any of the matrix stack functions */ + + /* sokol-gfx resources */ + sg_buffer vbuf; + sgl_pipeline def_pip; + sg_bindings bind; + + /* pipeline stack */ + int pip_tos; + sgl_pipeline pip_stack[_SGL_MAX_STACK_DEPTH]; + + /* matrix stacks */ + _sgl_matrix_mode_t cur_matrix_mode; + int matrix_tos[SGL_NUM_MATRIXMODES]; + _sgl_matrix_t matrix_stack[SGL_NUM_MATRIXMODES][_SGL_MAX_STACK_DEPTH]; +} _sgl_context_t; + +typedef struct { + _sgl_pool_t pool; + _sgl_context_t* contexts; +} _sgl_context_pool_t; + +typedef struct { + uint32_t init_cookie; + sgl_desc_t desc; + sg_image def_img; // a default white texture + sg_sampler def_smp; // a default sampler + sg_shader shd; // same shader for all contexts + sgl_context def_ctx_id; + sgl_context cur_ctx_id; + _sgl_context_t* cur_ctx; // may be 0! + _sgl_pipeline_pool_t pip_pool; + _sgl_context_pool_t context_pool; +} _sgl_t; +static _sgl_t _sgl; + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SGL_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sgl_log_messages[] = { + _SGL_LOG_ITEMS +}; +#undef _SGL_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SGL_PANIC(code) _sgl_log(SGL_LOGITEM_ ##code, 0, __LINE__) +#define _SGL_ERROR(code) _sgl_log(SGL_LOGITEM_ ##code, 1, __LINE__) +#define _SGL_WARN(code) _sgl_log(SGL_LOGITEM_ ##code, 2, __LINE__) +#define _SGL_INFO(code) _sgl_log(SGL_LOGITEM_ ##code, 3, __LINE__) + +static void _sgl_log(sgl_log_item_t log_item, uint32_t log_level, uint32_t line_nr) { + if (_sgl.desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _sgl_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _sgl.desc.logger.func("sgl", log_level, (uint32_t)log_item, message, line_nr, filename, _sgl.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +static void _sgl_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _sgl_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sgl.desc.allocator.alloc_fn) { + ptr = _sgl.desc.allocator.alloc_fn(size, _sgl.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SGL_PANIC(MALLOC_FAILED); + } + return ptr; +} + +static void* _sgl_malloc_clear(size_t size) { + void* ptr = _sgl_malloc(size); + _sgl_clear(ptr, size); + return ptr; +} + +static void _sgl_free(void* ptr) { + if (_sgl.desc.allocator.free_fn) { + _sgl.desc.allocator.free_fn(ptr, _sgl.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +static void _sgl_init_pool(_sgl_pool_t* pool, int num) { + SOKOL_ASSERT(pool && (num >= 1)); + /* slot 0 is reserved for the 'invalid id', so bump the pool size by 1 */ + pool->size = num + 1; + pool->queue_top = 0; + /* generation counters indexable by pool slot index, slot 0 is reserved */ + size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; + pool->gen_ctrs = (uint32_t*) _sgl_malloc_clear(gen_ctrs_size); + /* it's not a bug to only reserve 'num' here */ + pool->free_queue = (int*) _sgl_malloc_clear(sizeof(int) * (size_t)num); + /* never allocate the zero-th pool item since the invalid id is 0 */ + for (int i = pool->size-1; i >= 1; i--) { + pool->free_queue[pool->queue_top++] = i; + } +} + +static void _sgl_discard_pool(_sgl_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _sgl_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _sgl_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +static int _sgl_pool_alloc_index(_sgl_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + if (pool->queue_top > 0) { + int slot_index = pool->free_queue[--pool->queue_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return slot_index; + } else { + // pool exhausted + return _SGL_INVALID_SLOT_INDEX; + } +} + +static void _sgl_pool_free_index(_sgl_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + SOKOL_ASSERT(pool->queue_top < pool->size); + #ifdef SOKOL_DEBUG + /* debug check against double-free */ + for (int i = 0; i < pool->queue_top; i++) { + SOKOL_ASSERT(pool->free_queue[i] != slot_index); + } + #endif + pool->free_queue[pool->queue_top++] = slot_index; + SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); +} + +/* allocate the slot at slot_index: + - bump the slot's generation counter + - create a resource id from the generation counter and slot index + - set the slot's id to this id + - set the slot's state to ALLOC + - return the resource id +*/ +static uint32_t _sgl_slot_alloc(_sgl_pool_t* pool, _sgl_slot_t* slot, int slot_index) { + /* FIXME: add handling for an overflowing generation counter, + for now, just overflow (another option is to disable + the slot) + */ + SOKOL_ASSERT(pool && pool->gen_ctrs); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT((slot->state == SG_RESOURCESTATE_INITIAL) && (slot->id == SG_INVALID_ID)); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SGL_SLOT_SHIFT)|(slot_index & _SGL_SLOT_MASK); + slot->state = SG_RESOURCESTATE_ALLOC; + return slot->id; +} + +/* extract slot index from id */ +static int _sgl_slot_index(uint32_t id) { + int slot_index = (int) (id & _SGL_SLOT_MASK); + SOKOL_ASSERT(_SGL_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +// ██████ ██ ██████ ███████ ██ ██ ███ ██ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██████ ██ ██████ █████ ██ ██ ██ ██ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ███████ ██ ██ ████ ███████ ███████ +// +// >>pipelines +static void _sgl_reset_pipeline(_sgl_pipeline_t* pip) { + SOKOL_ASSERT(pip); + _sgl_clear(pip, sizeof(_sgl_pipeline_t)); +} + +static void _sgl_setup_pipeline_pool(int pool_size) { + /* note: the pools here will have an additional item, since slot 0 is reserved */ + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SGL_MAX_POOL_SIZE)); + _sgl_init_pool(&_sgl.pip_pool.pool, pool_size); + size_t pool_byte_size = sizeof(_sgl_pipeline_t) * (size_t)_sgl.pip_pool.pool.size; + _sgl.pip_pool.pips = (_sgl_pipeline_t*) _sgl_malloc_clear(pool_byte_size); +} + +static void _sgl_discard_pipeline_pool(void) { + SOKOL_ASSERT(0 != _sgl.pip_pool.pips); + _sgl_free(_sgl.pip_pool.pips); _sgl.pip_pool.pips = 0; + _sgl_discard_pool(&_sgl.pip_pool.pool); +} + +/* get pipeline pointer without id-check */ +static _sgl_pipeline_t* _sgl_pipeline_at(uint32_t pip_id) { + SOKOL_ASSERT(SG_INVALID_ID != pip_id); + int slot_index = _sgl_slot_index(pip_id); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < _sgl.pip_pool.pool.size)); + return &_sgl.pip_pool.pips[slot_index]; +} + +/* get pipeline pointer with id-check, returns 0 if no match */ +static _sgl_pipeline_t* _sgl_lookup_pipeline(uint32_t pip_id) { + if (SG_INVALID_ID != pip_id) { + _sgl_pipeline_t* pip = _sgl_pipeline_at(pip_id); + if (pip->slot.id == pip_id) { + return pip; + } + } + return 0; +} + +/* make pipeline id from uint32_t id */ +static sgl_pipeline _sgl_make_pip_id(uint32_t pip_id) { + sgl_pipeline pip = { pip_id }; + return pip; +} + +static sgl_pipeline _sgl_alloc_pipeline(void) { + sgl_pipeline res; + int slot_index = _sgl_pool_alloc_index(&_sgl.pip_pool.pool); + if (_SGL_INVALID_SLOT_INDEX != slot_index) { + res = _sgl_make_pip_id(_sgl_slot_alloc(&_sgl.pip_pool.pool, &_sgl.pip_pool.pips[slot_index].slot, slot_index)); + } else { + /* pool is exhausted */ + res = _sgl_make_pip_id(SG_INVALID_ID); + } + return res; +} + +static void _sgl_init_pipeline(sgl_pipeline pip_id, const sg_pipeline_desc* in_desc, const sgl_context_desc_t* ctx_desc) { + SOKOL_ASSERT((pip_id.id != SG_INVALID_ID) && in_desc && ctx_desc); + + /* create a new desc with 'patched' shader and pixel format state */ + sg_pipeline_desc desc = *in_desc; + desc.layout.buffers[0].stride = sizeof(_sgl_vertex_t); + { + sg_vertex_attr_state* pos = &desc.layout.attrs[0]; + pos->offset = offsetof(_sgl_vertex_t, pos); + pos->format = SG_VERTEXFORMAT_FLOAT3; + } + { + sg_vertex_attr_state* uv = &desc.layout.attrs[1]; + uv->offset = offsetof(_sgl_vertex_t, uv); + uv->format = SG_VERTEXFORMAT_FLOAT2; + } + { + sg_vertex_attr_state* rgba = &desc.layout.attrs[2]; + rgba->offset = offsetof(_sgl_vertex_t, rgba); + rgba->format = SG_VERTEXFORMAT_UBYTE4N; + } + { + sg_vertex_attr_state* psize = &desc.layout.attrs[3]; + psize->offset = offsetof(_sgl_vertex_t, psize); + psize->format = SG_VERTEXFORMAT_FLOAT; + } + if (in_desc->shader.id == SG_INVALID_ID) { + desc.shader = _sgl.shd; + } + desc.index_type = SG_INDEXTYPE_NONE; + desc.sample_count = ctx_desc->sample_count; + if (desc.face_winding == _SG_FACEWINDING_DEFAULT) { + desc.face_winding = _sgl.desc.face_winding; + } + desc.depth.pixel_format = ctx_desc->depth_format; + if (ctx_desc->depth_format == SG_PIXELFORMAT_NONE) { + desc.depth.write_enabled = false; + } + desc.colors[0].pixel_format = ctx_desc->color_format; + if (desc.colors[0].write_mask == _SG_COLORMASK_DEFAULT) { + desc.colors[0].write_mask = SG_COLORMASK_RGB; + } + + _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); + SOKOL_ASSERT(pip && (pip->slot.state == SG_RESOURCESTATE_ALLOC)); + pip->slot.state = SG_RESOURCESTATE_VALID; + for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { + switch (i) { + case SGL_PRIMITIVETYPE_POINTS: + desc.primitive_type = SG_PRIMITIVETYPE_POINTS; + break; + case SGL_PRIMITIVETYPE_LINES: + desc.primitive_type = SG_PRIMITIVETYPE_LINES; + break; + case SGL_PRIMITIVETYPE_LINE_STRIP: + desc.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP; + break; + case SGL_PRIMITIVETYPE_TRIANGLES: + desc.primitive_type = SG_PRIMITIVETYPE_TRIANGLES; + break; + case SGL_PRIMITIVETYPE_TRIANGLE_STRIP: + case SGL_PRIMITIVETYPE_QUADS: + desc.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP; + break; + } + if (SGL_PRIMITIVETYPE_QUADS == i) { + /* quads are emulated via triangles, use the same pipeline object */ + pip->pip[i] = pip->pip[SGL_PRIMITIVETYPE_TRIANGLES]; + } else { + pip->pip[i] = sg_make_pipeline(&desc); + if (pip->pip[i].id == SG_INVALID_ID) { + _SGL_ERROR(MAKE_PIPELINE_FAILED); + pip->slot.state = SG_RESOURCESTATE_FAILED; + } + } + } +} + +static sgl_pipeline _sgl_make_pipeline(const sg_pipeline_desc* desc, const sgl_context_desc_t* ctx_desc) { + SOKOL_ASSERT(desc && ctx_desc); + sgl_pipeline pip_id = _sgl_alloc_pipeline(); + if (pip_id.id != SG_INVALID_ID) { + _sgl_init_pipeline(pip_id, desc, ctx_desc); + } else { + _SGL_ERROR(PIPELINE_POOL_EXHAUSTED); + } + return pip_id; +} + +static void _sgl_destroy_pipeline(sgl_pipeline pip_id) { + _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); + if (pip) { + sg_push_debug_group("sokol-gl"); + for (int i = 0; i < SGL_NUM_PRIMITIVE_TYPES; i++) { + if (i != SGL_PRIMITIVETYPE_QUADS) { + sg_destroy_pipeline(pip->pip[i]); + } + } + sg_pop_debug_group(); + _sgl_reset_pipeline(pip); + _sgl_pool_free_index(&_sgl.pip_pool.pool, _sgl_slot_index(pip_id.id)); + } +} + +static sg_pipeline _sgl_get_pipeline(sgl_pipeline pip_id, _sgl_primitive_type_t prim_type) { + _sgl_pipeline_t* pip = _sgl_lookup_pipeline(pip_id.id); + if (pip) { + return pip->pip[prim_type]; + } else { + sg_pipeline dummy_id = { SG_INVALID_ID }; + return dummy_id; + } +} + +// ██████ ██████ ███ ██ ████████ ███████ ██ ██ ████████ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ █████ ███ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ████ ██ ███████ ██ ██ ██ ███████ +// +// >>contexts +static void _sgl_reset_context(_sgl_context_t* ctx) { + SOKOL_ASSERT(ctx); + SOKOL_ASSERT(0 == ctx->vertices.ptr); + SOKOL_ASSERT(0 == ctx->uniforms.ptr); + SOKOL_ASSERT(0 == ctx->commands.ptr); + _sgl_clear(ctx, sizeof(_sgl_context_t)); +} + +static void _sgl_setup_context_pool(int pool_size) { + /* note: the pools here will have an additional item, since slot 0 is reserved */ + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SGL_MAX_POOL_SIZE)); + _sgl_init_pool(&_sgl.context_pool.pool, pool_size); + size_t pool_byte_size = sizeof(_sgl_context_t) * (size_t)_sgl.context_pool.pool.size; + _sgl.context_pool.contexts = (_sgl_context_t*) _sgl_malloc_clear(pool_byte_size); +} + +static void _sgl_discard_context_pool(void) { + SOKOL_ASSERT(0 != _sgl.context_pool.contexts); + _sgl_free(_sgl.context_pool.contexts); _sgl.context_pool.contexts = 0; + _sgl_discard_pool(&_sgl.context_pool.pool); +} + +// get context pointer without id-check +static _sgl_context_t* _sgl_context_at(uint32_t ctx_id) { + SOKOL_ASSERT(SG_INVALID_ID != ctx_id); + int slot_index = _sgl_slot_index(ctx_id); + SOKOL_ASSERT((slot_index > _SGL_INVALID_SLOT_INDEX) && (slot_index < _sgl.context_pool.pool.size)); + return &_sgl.context_pool.contexts[slot_index]; +} + +// get context pointer with id-check, returns 0 if no match +static _sgl_context_t* _sgl_lookup_context(uint32_t ctx_id) { + if (SG_INVALID_ID != ctx_id) { + _sgl_context_t* ctx = _sgl_context_at(ctx_id); + if (ctx->slot.id == ctx_id) { + return ctx; + } + } + return 0; +} + +// make context id from uint32_t id +static sgl_context _sgl_make_ctx_id(uint32_t ctx_id) { + sgl_context ctx = { ctx_id }; + return ctx; +} + +static sgl_context _sgl_alloc_context(void) { + sgl_context res; + int slot_index = _sgl_pool_alloc_index(&_sgl.context_pool.pool); + if (_SGL_INVALID_SLOT_INDEX != slot_index) { + res = _sgl_make_ctx_id(_sgl_slot_alloc(&_sgl.context_pool.pool, &_sgl.context_pool.contexts[slot_index].slot, slot_index)); + } else { + // pool is exhausted + res = _sgl_make_ctx_id(SG_INVALID_ID); + } + return res; +} + +// return sgl_context_desc_t with patched defaults +static sgl_context_desc_t _sgl_context_desc_defaults(const sgl_context_desc_t* desc) { + sgl_context_desc_t res = *desc; + res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES); + res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS); + return res; +} + +static void _sgl_identity(_sgl_matrix_t*); +static sg_commit_listener _sgl_make_commit_listener(_sgl_context_t* ctx); +static void _sgl_init_context(sgl_context ctx_id, const sgl_context_desc_t* in_desc) { + SOKOL_ASSERT((ctx_id.id != SG_INVALID_ID) && in_desc); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + SOKOL_ASSERT(ctx); + ctx->desc = _sgl_context_desc_defaults(in_desc); + // NOTE: frame_id must be non-zero, so that updates trigger in first frame + ctx->frame_id = 1; + ctx->cur_img = _sgl.def_img; + ctx->cur_smp = _sgl.def_smp; + + // allocate buffers and pools + ctx->vertices.cap = ctx->desc.max_vertices; + ctx->commands.cap = ctx->uniforms.cap = ctx->desc.max_commands; + ctx->vertices.ptr = (_sgl_vertex_t*) _sgl_malloc((size_t)ctx->vertices.cap * sizeof(_sgl_vertex_t)); + ctx->uniforms.ptr = (_sgl_uniform_t*) _sgl_malloc((size_t)ctx->uniforms.cap * sizeof(_sgl_uniform_t)); + ctx->commands.ptr = (_sgl_command_t*) _sgl_malloc((size_t)ctx->commands.cap * sizeof(_sgl_command_t)); + + // create sokol-gfx resource objects + sg_push_debug_group("sokol-gl"); + + sg_buffer_desc vbuf_desc; + _sgl_clear(&vbuf_desc, sizeof(vbuf_desc)); + vbuf_desc.size = (size_t)ctx->vertices.cap * sizeof(_sgl_vertex_t); + vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vbuf_desc.usage = SG_USAGE_STREAM; + vbuf_desc.label = "sgl-vertex-buffer"; + ctx->vbuf = sg_make_buffer(&vbuf_desc); + SOKOL_ASSERT(SG_INVALID_ID != ctx->vbuf.id); + ctx->bind.vertex_buffers[0] = ctx->vbuf; + + sg_pipeline_desc def_pip_desc; + _sgl_clear(&def_pip_desc, sizeof(def_pip_desc)); + def_pip_desc.depth.write_enabled = true; + ctx->def_pip = _sgl_make_pipeline(&def_pip_desc, &ctx->desc); + if (!sg_add_commit_listener(_sgl_make_commit_listener(ctx))) { + _SGL_ERROR(ADD_COMMIT_LISTENER_FAILED); + } + sg_pop_debug_group(); + + // default state + ctx->rgba = 0xFFFFFFFF; + ctx->point_size = 1.0f; + for (int i = 0; i < SGL_NUM_MATRIXMODES; i++) { + _sgl_identity(&ctx->matrix_stack[i][0]); + } + ctx->pip_stack[0] = ctx->def_pip; + ctx->matrix_dirty = true; +} + +static sgl_context _sgl_make_context(const sgl_context_desc_t* desc) { + SOKOL_ASSERT(desc); + sgl_context ctx_id = _sgl_alloc_context(); + if (ctx_id.id != SG_INVALID_ID) { + _sgl_init_context(ctx_id, desc); + } else { + _SGL_ERROR(CONTEXT_POOL_EXHAUSTED); + } + return ctx_id; +} + +static void _sgl_destroy_context(sgl_context ctx_id) { + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + SOKOL_ASSERT(ctx->vertices.ptr); + SOKOL_ASSERT(ctx->uniforms.ptr); + SOKOL_ASSERT(ctx->commands.ptr); + + _sgl_free(ctx->vertices.ptr); + _sgl_free(ctx->uniforms.ptr); + _sgl_free(ctx->commands.ptr); + ctx->vertices.ptr = 0; + ctx->uniforms.ptr = 0; + ctx->commands.ptr = 0; + + sg_push_debug_group("sokol-gl"); + sg_destroy_buffer(ctx->vbuf); + _sgl_destroy_pipeline(ctx->def_pip); + sg_remove_commit_listener(_sgl_make_commit_listener(ctx)); + sg_pop_debug_group(); + + _sgl_reset_context(ctx); + _sgl_pool_free_index(&_sgl.context_pool.pool, _sgl_slot_index(ctx_id.id)); + } +} + +// ███ ███ ██ ███████ ██████ +// ████ ████ ██ ██ ██ +// ██ ████ ██ ██ ███████ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ██████ +// +// >>misc + +static sgl_error_t _sgl_error_defaults(void) { + sgl_error_t defaults; + _sgl_clear(&defaults, sizeof(defaults)); + return defaults; +} + +static int _sgl_num_vertices(_sgl_context_t* ctx) { + return ctx->vertices.next; +} + +static int _sgl_num_commands(_sgl_context_t* ctx) { + return ctx->commands.next; +} + +static void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) { + ctx->in_begin = true; + ctx->base_vertex = ctx->vertices.next; + ctx->quad_vtx_count = 0; + ctx->cur_prim_type = mode; +} + +static void _sgl_rewind(_sgl_context_t* ctx) { + ctx->frame_id++; + ctx->vertices.next = 0; + ctx->uniforms.next = 0; + ctx->commands.next = 0; + ctx->base_vertex = 0; + ctx->error = _sgl_error_defaults(); + ctx->layer_id = 0; + ctx->matrix_dirty = true; +} + +// called from inside sokol-gfx sg_commit() +static void _sgl_commit_listener(void* userdata) { + _sgl_context_t* ctx = _sgl_lookup_context((uint32_t)(uintptr_t)userdata); + if (ctx) { + _sgl_rewind(ctx); + } +} + +static sg_commit_listener _sgl_make_commit_listener(_sgl_context_t* ctx) { + sg_commit_listener listener = { _sgl_commit_listener, (void*)(uintptr_t)(ctx->slot.id) }; + return listener; +} + +static _sgl_vertex_t* _sgl_next_vertex(_sgl_context_t* ctx) { + if (ctx->vertices.next < ctx->vertices.cap) { + return &ctx->vertices.ptr[ctx->vertices.next++]; + } else { + ctx->error.vertices_full = true; + ctx->error.any = true; + return 0; + } +} + +static _sgl_uniform_t* _sgl_next_uniform(_sgl_context_t* ctx) { + if (ctx->uniforms.next < ctx->uniforms.cap) { + return &ctx->uniforms.ptr[ctx->uniforms.next++]; + } else { + ctx->error.uniforms_full = true; + ctx->error.any = true; + return 0; + } +} + +static _sgl_command_t* _sgl_cur_command(_sgl_context_t* ctx) { + if (ctx->commands.next > 0) { + return &ctx->commands.ptr[ctx->commands.next - 1]; + } else { + return 0; + } +} + +static _sgl_command_t* _sgl_next_command(_sgl_context_t* ctx) { + if (ctx->commands.next < ctx->commands.cap) { + return &ctx->commands.ptr[ctx->commands.next++]; + } else { + ctx->error.commands_full = true; + ctx->error.any = true; + return 0; + } +} + +static uint32_t _sgl_pack_rgbab(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return (uint32_t)(((uint32_t)a<<24)|((uint32_t)b<<16)|((uint32_t)g<<8)|r); +} + +static float _sgl_clamp(float v, float lo, float hi) { + if (v < lo) return lo; + else if (v > hi) return hi; + else return v; +} + +static uint32_t _sgl_pack_rgbaf(float r, float g, float b, float a) { + uint8_t r_u8 = (uint8_t) (_sgl_clamp(r, 0.0f, 1.0f) * 255.0f); + uint8_t g_u8 = (uint8_t) (_sgl_clamp(g, 0.0f, 1.0f) * 255.0f); + uint8_t b_u8 = (uint8_t) (_sgl_clamp(b, 0.0f, 1.0f) * 255.0f); + uint8_t a_u8 = (uint8_t) (_sgl_clamp(a, 0.0f, 1.0f) * 255.0f); + return _sgl_pack_rgbab(r_u8, g_u8, b_u8, a_u8); +} + +static void _sgl_vtx(_sgl_context_t* ctx, float x, float y, float z, float u, float v, uint32_t rgba) { + SOKOL_ASSERT(ctx->in_begin); + _sgl_vertex_t* vtx; + /* handle non-native primitive types */ + if ((ctx->cur_prim_type == SGL_PRIMITIVETYPE_QUADS) && ((ctx->quad_vtx_count & 3) == 3)) { + /* for quads, before writing the last quad vertex, reuse + the first and third vertex to start the second triangle in the quad + */ + vtx = _sgl_next_vertex(ctx); + if (vtx) { *vtx = *(vtx - 3); } + vtx = _sgl_next_vertex(ctx); + if (vtx) { *vtx = *(vtx - 2); } + } + vtx = _sgl_next_vertex(ctx); + if (vtx) { + vtx->pos[0] = x; vtx->pos[1] = y; vtx->pos[2] = z; + vtx->uv[0] = u; vtx->uv[1] = v; + vtx->rgba = rgba; + vtx->psize = ctx->point_size; + } + ctx->quad_vtx_count++; +} + +static void _sgl_identity(_sgl_matrix_t* m) { + for (int c = 0; c < 4; c++) { + for (int r = 0; r < 4; r++) { + m->v[c][r] = (r == c) ? 1.0f : 0.0f; + } + } +} + +static void _sgl_transpose(_sgl_matrix_t* dst, const _sgl_matrix_t* m) { + SOKOL_ASSERT(dst != m); + for (int c = 0; c < 4; c++) { + for (int r = 0; r < 4; r++) { + dst->v[r][c] = m->v[c][r]; + } + } +} + +/* _sgl_rotate, _sgl_frustum, _sgl_ortho from MESA m_matric.c */ +static void _sgl_matmul4(_sgl_matrix_t* p, const _sgl_matrix_t* a, const _sgl_matrix_t* b) { + for (int r = 0; r < 4; r++) { + float ai0=a->v[0][r], ai1=a->v[1][r], ai2=a->v[2][r], ai3=a->v[3][r]; + p->v[0][r] = ai0*b->v[0][0] + ai1*b->v[0][1] + ai2*b->v[0][2] + ai3*b->v[0][3]; + p->v[1][r] = ai0*b->v[1][0] + ai1*b->v[1][1] + ai2*b->v[1][2] + ai3*b->v[1][3]; + p->v[2][r] = ai0*b->v[2][0] + ai1*b->v[2][1] + ai2*b->v[2][2] + ai3*b->v[2][3]; + p->v[3][r] = ai0*b->v[3][0] + ai1*b->v[3][1] + ai2*b->v[3][2] + ai3*b->v[3][3]; + } +} + +static void _sgl_mul(_sgl_matrix_t* dst, const _sgl_matrix_t* m) { + _sgl_matmul4(dst, dst, m); +} + +static void _sgl_rotate(_sgl_matrix_t* dst, float a, float x, float y, float z) { + + float s = sinf(a); + float c = cosf(a); + + float mag = sqrtf(x*x + y*y + z*z); + if (mag < 1.0e-4F) { + return; + } + x /= mag; + y /= mag; + z /= mag; + float xx = x * x; + float yy = y * y; + float zz = z * z; + float xy = x * y; + float yz = y * z; + float zx = z * x; + float xs = x * s; + float ys = y * s; + float zs = z * s; + float one_c = 1.0f - c; + + _sgl_matrix_t m; + m.v[0][0] = (one_c * xx) + c; + m.v[1][0] = (one_c * xy) - zs; + m.v[2][0] = (one_c * zx) + ys; + m.v[3][0] = 0.0f; + m.v[0][1] = (one_c * xy) + zs; + m.v[1][1] = (one_c * yy) + c; + m.v[2][1] = (one_c * yz) - xs; + m.v[3][1] = 0.0f; + m.v[0][2] = (one_c * zx) - ys; + m.v[1][2] = (one_c * yz) + xs; + m.v[2][2] = (one_c * zz) + c; + m.v[3][2] = 0.0f; + m.v[0][3] = 0.0f; + m.v[1][3] = 0.0f; + m.v[2][3] = 0.0f; + m.v[3][3] = 1.0f; + _sgl_mul(dst, &m); +} + +static void _sgl_scale(_sgl_matrix_t* dst, float x, float y, float z) { + for (int r = 0; r < 4; r++) { + dst->v[0][r] *= x; + dst->v[1][r] *= y; + dst->v[2][r] *= z; + } +} + +static void _sgl_translate(_sgl_matrix_t* dst, float x, float y, float z) { + for (int r = 0; r < 4; r++) { + dst->v[3][r] = dst->v[0][r]*x + dst->v[1][r]*y + dst->v[2][r]*z + dst->v[3][r]; + } +} + +static void _sgl_frustum(_sgl_matrix_t* dst, float left, float right, float bottom, float top, float znear, float zfar) { + float x = (2.0f * znear) / (right - left); + float y = (2.0f * znear) / (top - bottom); + float a = (right + left) / (right - left); + float b = (top + bottom) / (top - bottom); + float c = -(zfar + znear) / (zfar - znear); + float d = -(2.0f * zfar * znear) / (zfar - znear); + _sgl_matrix_t m; + m.v[0][0] = x; m.v[0][1] = 0.0f; m.v[0][2] = 0.0f; m.v[0][3] = 0.0f; + m.v[1][0] = 0.0f; m.v[1][1] = y; m.v[1][2] = 0.0f; m.v[1][3] = 0.0f; + m.v[2][0] = a; m.v[2][1] = b; m.v[2][2] = c; m.v[2][3] = -1.0f; + m.v[3][0] = 0.0f; m.v[3][1] = 0.0f; m.v[3][2] = d; m.v[3][3] = 0.0f; + _sgl_mul(dst, &m); +} + +static void _sgl_ortho(_sgl_matrix_t* dst, float left, float right, float bottom, float top, float znear, float zfar) { + _sgl_matrix_t m; + m.v[0][0] = 2.0f / (right - left); + m.v[1][0] = 0.0f; + m.v[2][0] = 0.0f; + m.v[3][0] = -(right + left) / (right - left); + m.v[0][1] = 0.0f; + m.v[1][1] = 2.0f / (top - bottom); + m.v[2][1] = 0.0f; + m.v[3][1] = -(top + bottom) / (top - bottom); + m.v[0][2] = 0.0f; + m.v[1][2] = 0.0f; + m.v[2][2] = -2.0f / (zfar - znear); + m.v[3][2] = -(zfar + znear) / (zfar - znear); + m.v[0][3] = 0.0f; + m.v[1][3] = 0.0f; + m.v[2][3] = 0.0f; + m.v[3][3] = 1.0f; + + _sgl_mul(dst, &m); +} + +/* _sgl_perspective, _sgl_lookat from Regal project.c */ +static void _sgl_perspective(_sgl_matrix_t* dst, float fovy, float aspect, float znear, float zfar) { + float sine = sinf(fovy / 2.0f); + float delta_z = zfar - znear; + if ((delta_z == 0.0f) || (sine == 0.0f) || (aspect == 0.0f)) { + return; + } + float cotan = cosf(fovy / 2.0f) / sine; + _sgl_matrix_t m; + _sgl_identity(&m); + m.v[0][0] = cotan / aspect; + m.v[1][1] = cotan; + m.v[2][2] = -(zfar + znear) / delta_z; + m.v[2][3] = -1.0f; + m.v[3][2] = -2.0f * znear * zfar / delta_z; + m.v[3][3] = 0.0f; + _sgl_mul(dst, &m); +} + +static void _sgl_normalize(float v[3]) { + float r = sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); + if (r == 0.0f) { + return; + } + v[0] /= r; + v[1] /= r; + v[2] /= r; +} + +static void _sgl_cross(float v1[3], float v2[3], float res[3]) { + res[0] = v1[1]*v2[2] - v1[2]*v2[1]; + res[1] = v1[2]*v2[0] - v1[0]*v2[2]; + res[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +static void _sgl_lookat(_sgl_matrix_t* dst, + float eye_x, float eye_y, float eye_z, + float center_x, float center_y, float center_z, + float up_x, float up_y, float up_z) +{ + float fwd[3], side[3], up[3]; + + fwd[0] = center_x - eye_x; fwd[1] = center_y - eye_y; fwd[2] = center_z - eye_z; + up[0] = up_x; up[1] = up_y; up[2] = up_z; + _sgl_normalize(fwd); + _sgl_cross(fwd, up, side); + _sgl_normalize(side); + _sgl_cross(side, fwd, up); + + _sgl_matrix_t m; + _sgl_identity(&m); + m.v[0][0] = side[0]; + m.v[1][0] = side[1]; + m.v[2][0] = side[2]; + m.v[0][1] = up[0]; + m.v[1][1] = up[1]; + m.v[2][1] = up[2]; + m.v[0][2] = -fwd[0]; + m.v[1][2] = -fwd[1]; + m.v[2][2] = -fwd[2]; + _sgl_mul(dst, &m); + _sgl_translate(dst, -eye_x, -eye_y, -eye_z); +} + +/* current top-of-stack projection matrix */ +static _sgl_matrix_t* _sgl_matrix_projection(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_PROJECTION][ctx->matrix_tos[SGL_MATRIXMODE_PROJECTION]]; +} + +/* get top-of-stack modelview matrix */ +static _sgl_matrix_t* _sgl_matrix_modelview(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_MODELVIEW][ctx->matrix_tos[SGL_MATRIXMODE_MODELVIEW]]; +} + +/* get top-of-stack texture matrix */ +static _sgl_matrix_t* _sgl_matrix_texture(_sgl_context_t* ctx) { + return &ctx->matrix_stack[SGL_MATRIXMODE_TEXTURE][ctx->matrix_tos[SGL_MATRIXMODE_TEXTURE]]; +} + +/* get pointer to current top-of-stack of current matrix mode */ +static _sgl_matrix_t* _sgl_matrix(_sgl_context_t* ctx) { + return &ctx->matrix_stack[ctx->cur_matrix_mode][ctx->matrix_tos[ctx->cur_matrix_mode]]; +} + +static sgl_desc_t _sgl_desc_defaults(const sgl_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sgl_desc_t res = *desc; + res.max_vertices = _sgl_def(desc->max_vertices, _SGL_DEFAULT_MAX_VERTICES); + res.max_commands = _sgl_def(desc->max_commands, _SGL_DEFAULT_MAX_COMMANDS); + res.context_pool_size = _sgl_def(desc->context_pool_size, _SGL_DEFAULT_CONTEXT_POOL_SIZE); + res.pipeline_pool_size = _sgl_def(desc->pipeline_pool_size, _SGL_DEFAULT_PIPELINE_POOL_SIZE); + res.face_winding = _sgl_def(desc->face_winding, SG_FACEWINDING_CCW); + return res; +} + +// create resources which are shared between all contexts +static void _sgl_setup_common(void) { + sg_push_debug_group("sokol-gl"); + + uint32_t pixels[64]; + for (int i = 0; i < 64; i++) { + pixels[i] = 0xFFFFFFFF; + } + sg_image_desc img_desc; + _sgl_clear(&img_desc, sizeof(img_desc)); + img_desc.type = SG_IMAGETYPE_2D; + img_desc.width = 8; + img_desc.height = 8; + img_desc.num_mipmaps = 1; + img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; + img_desc.data.subimage[0][0] = SG_RANGE(pixels); + img_desc.label = "sgl-default-texture"; + _sgl.def_img = sg_make_image(&img_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_img.id); + + sg_sampler_desc smp_desc; + _sgl_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.min_filter = SG_FILTER_NEAREST; + smp_desc.mag_filter = SG_FILTER_NEAREST; + _sgl.def_smp = sg_make_sampler(&smp_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.def_smp.id); + + // one shader for all contexts + sg_shader_desc shd_desc; + _sgl_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].glsl_name = "position"; + shd_desc.attrs[1].glsl_name = "texcoord0"; + shd_desc.attrs[2].glsl_name = "color0"; + shd_desc.attrs[3].glsl_name = "psize"; + shd_desc.attrs[0].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[0].hlsl_sem_index = 0; + shd_desc.attrs[1].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[1].hlsl_sem_index = 1; + shd_desc.attrs[2].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[2].hlsl_sem_index = 2; + shd_desc.attrs[3].hlsl_sem_name = "TEXCOORD"; + shd_desc.attrs[3].hlsl_sem_index = 3; + shd_desc.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX; + shd_desc.uniform_blocks[0].size = sizeof(_sgl_uniform_t); + shd_desc.uniform_blocks[0].hlsl_register_b_n = 0; + shd_desc.uniform_blocks[0].msl_buffer_n = 0; + shd_desc.uniform_blocks[0].wgsl_group0_binding_n = 0; + shd_desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "vs_params"; + shd_desc.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + shd_desc.uniform_blocks[0].glsl_uniforms[0].array_count = 8; + shd_desc.images[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.images[0].hlsl_register_t_n = 0; + shd_desc.images[0].msl_texture_n = 0; + shd_desc.images[0].wgsl_group1_binding_n = 64; + shd_desc.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.samplers[0].hlsl_register_s_n = 0; + shd_desc.samplers[0].msl_sampler_n = 0; + shd_desc.samplers[0].wgsl_group1_binding_n = 80; + shd_desc.image_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT; + shd_desc.image_sampler_pairs[0].image_slot = 0; + shd_desc.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.image_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.label = "sgl-shader"; + #if defined(SOKOL_GLCORE) + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_glsl410; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_glsl410; + #elif defined(SOKOL_GLES3) + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_glsl300es; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vertex_func.entry = "main0"; + shd_desc.fragment_func.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_macos); + shd_desc.fragment_func.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vertex_func.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_ios); + shd_desc.fragment_func.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_ios); + break; + default: + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_metal_sim; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vertex_func.bytecode = SG_RANGE(_sgl_vs_bytecode_hlsl4); + shd_desc.fragment_func.bytecode = SG_RANGE(_sgl_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vertex_func.source = (const char*)_sgl_vs_source_wgsl; + shd_desc.fragment_func.source = (const char*)_sgl_fs_source_wgsl; + #else + shd_desc.vertex_func.source = _sgl_vs_source_dummy; + shd_desc.fragment_func.source = _sgl_fs_source_dummy; + #endif + _sgl.shd = sg_make_shader(&shd_desc); + SOKOL_ASSERT(SG_INVALID_ID != _sgl.shd.id); + sg_pop_debug_group(); +} + +// discard resources which are shared between all contexts +static void _sgl_discard_common(void) { + sg_push_debug_group("sokol-gl"); + sg_destroy_image(_sgl.def_img); + sg_destroy_sampler(_sgl.def_smp); + sg_destroy_shader(_sgl.shd); + sg_pop_debug_group(); +} + +static bool _sgl_is_default_context(sgl_context ctx_id) { + return ctx_id.id == SGL_DEFAULT_CONTEXT.id; +} + +static void _sgl_draw(_sgl_context_t* ctx, int layer_id) { + SOKOL_ASSERT(ctx); + if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) { + sg_push_debug_group("sokol-gl"); + + uint32_t cur_pip_id = SG_INVALID_ID; + uint32_t cur_img_id = SG_INVALID_ID; + uint32_t cur_smp_id = SG_INVALID_ID; + int cur_uniform_index = -1; + + if (ctx->update_frame_id != ctx->frame_id) { + ctx->update_frame_id = ctx->frame_id; + const sg_range range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sgl_vertex_t) }; + sg_update_buffer(ctx->vbuf, &range); + } + + // render all successfully recorded commands (this may be less than the + // issued commands if we're in an error state) + for (int i = 0; i < ctx->commands.next; i++) { + const _sgl_command_t* cmd = &ctx->commands.ptr[i]; + if (cmd->layer_id != layer_id) { + continue; + } + switch (cmd->cmd) { + case SGL_COMMAND_VIEWPORT: + { + const _sgl_viewport_args_t* args = &cmd->args.viewport; + sg_apply_viewport(args->x, args->y, args->w, args->h, args->origin_top_left); + } + break; + case SGL_COMMAND_SCISSOR_RECT: + { + const _sgl_scissor_rect_args_t* args = &cmd->args.scissor_rect; + sg_apply_scissor_rect(args->x, args->y, args->w, args->h, args->origin_top_left); + } + break; + case SGL_COMMAND_DRAW: + { + const _sgl_draw_args_t* args = &cmd->args.draw; + if (args->pip.id != cur_pip_id) { + sg_apply_pipeline(args->pip); + cur_pip_id = args->pip.id; + /* when pipeline changes, also need to re-apply uniforms and bindings */ + cur_img_id = SG_INVALID_ID; + cur_smp_id = SG_INVALID_ID; + cur_uniform_index = -1; + } + if ((cur_img_id != args->img.id) || (cur_smp_id != args->smp.id)) { + ctx->bind.images[0] = args->img; + ctx->bind.samplers[0] = args->smp; + sg_apply_bindings(&ctx->bind); + cur_img_id = args->img.id; + cur_smp_id = args->smp.id; + } + if (cur_uniform_index != args->uniform_index) { + const sg_range ub_range = { &ctx->uniforms.ptr[args->uniform_index], sizeof(_sgl_uniform_t) }; + sg_apply_uniforms(0, &ub_range); + cur_uniform_index = args->uniform_index; + } + /* FIXME: what if number of vertices doesn't match the primitive type? */ + if (args->num_vertices > 0) { + sg_draw(args->base_vertex, args->num_vertices, 1); + } + } + break; + } + } + sg_pop_debug_group(); + } +} + +static sgl_context_desc_t _sgl_as_context_desc(const sgl_desc_t* desc) { + sgl_context_desc_t ctx_desc; + _sgl_clear(&ctx_desc, sizeof(ctx_desc)); + ctx_desc.max_vertices = desc->max_vertices; + ctx_desc.max_commands = desc->max_commands; + ctx_desc.color_format = desc->color_format; + ctx_desc.depth_format = desc->depth_format; + ctx_desc.sample_count = desc->sample_count; + return ctx_desc; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sgl_setup(const sgl_desc_t* desc) { + SOKOL_ASSERT(desc); + _sgl_clear(&_sgl, sizeof(_sgl)); + _sgl.init_cookie = _SGL_INIT_COOKIE; + _sgl.desc = _sgl_desc_defaults(desc); + _sgl_setup_pipeline_pool(_sgl.desc.pipeline_pool_size); + _sgl_setup_context_pool(_sgl.desc.context_pool_size); + _sgl_setup_common(); + const sgl_context_desc_t ctx_desc = _sgl_as_context_desc(&_sgl.desc); + _sgl.def_ctx_id = sgl_make_context(&ctx_desc); + SOKOL_ASSERT(SGL_DEFAULT_CONTEXT.id == _sgl.def_ctx_id.id); + sgl_set_context(_sgl.def_ctx_id); +} + +SOKOL_API_IMPL void sgl_shutdown(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + // contexts own a pipeline, so destroy contexts before pipelines + for (int i = 0; i < _sgl.context_pool.pool.size; i++) { + _sgl_context_t* ctx = &_sgl.context_pool.contexts[i]; + _sgl_destroy_context(_sgl_make_ctx_id(ctx->slot.id)); + } + for (int i = 0; i < _sgl.pip_pool.pool.size; i++) { + _sgl_pipeline_t* pip = &_sgl.pip_pool.pips[i]; + _sgl_destroy_pipeline(_sgl_make_pip_id(pip->slot.id)); + } + _sgl_discard_context_pool(); + _sgl_discard_pipeline_pool(); + _sgl_discard_common(); + _sgl.init_cookie = 0; +} + +SOKOL_API_IMPL sgl_error_t sgl_error(void) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return ctx->error; + } else { + sgl_error_t err = _sgl_error_defaults(); + err.no_context = true; + err.any = true; + return err; + } +} + +SOKOL_API_IMPL sgl_error_t sgl_context_error(sgl_context ctx_id) { + const _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + return ctx->error; + } else { + sgl_error_t err = _sgl_error_defaults(); + err.no_context = true; + err.any = true; + return err; + } +} + +SOKOL_API_IMPL float sgl_rad(float deg) { + return (deg * (float)M_PI) / 180.0f; +} + +SOKOL_API_IMPL float sgl_deg(float rad) { + return (rad * 180.0f) / (float)M_PI; +} + +SOKOL_API_IMPL sgl_context sgl_make_context(const sgl_context_desc_t* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + return _sgl_make_context(desc); +} + +SOKOL_API_IMPL void sgl_destroy_context(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + if (_sgl_is_default_context(ctx_id)) { + _SGL_WARN(CANNOT_DESTROY_DEFAULT_CONTEXT); + return; + } + _sgl_destroy_context(ctx_id); + // re-validate the current context pointer (this will return a nullptr + // if we just destroyed the current context) + _sgl.cur_ctx = _sgl_lookup_context(_sgl.cur_ctx_id.id); +} + +SOKOL_API_IMPL void sgl_set_context(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + if (_sgl_is_default_context(ctx_id)) { + _sgl.cur_ctx_id = _sgl.def_ctx_id; + } else { + _sgl.cur_ctx_id = ctx_id; + } + // this will return null if the handle isn't valid + _sgl.cur_ctx = _sgl_lookup_context(_sgl.cur_ctx_id.id); +} + +SOKOL_API_IMPL sgl_context sgl_get_context(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + return _sgl.cur_ctx_id; +} + +SOKOL_API_IMPL sgl_context sgl_default_context(void) { + return SGL_DEFAULT_CONTEXT; +} + +SOKOL_API_IMPL int sgl_num_vertices(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_num_vertices(ctx); + } else { + return 0; + } +} + +SOKOL_API_IMPL int sgl_num_commands(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_num_commands(ctx); + } else { + return 0; + } +} + +SOKOL_API_IMPL sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + return _sgl_make_pipeline(desc, &ctx->desc); + } else { + return _sgl_make_pip_id(SG_INVALID_ID); + } +} + +SOKOL_API_IMPL sgl_pipeline sgl_context_make_pipeline(sgl_context ctx_id, const sg_pipeline_desc* desc) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + const _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + return _sgl_make_pipeline(desc, &ctx->desc); + } else { + return _sgl_make_pip_id(SG_INVALID_ID); + } +} + +SOKOL_API_IMPL void sgl_destroy_pipeline(sgl_pipeline pip_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_destroy_pipeline(pip_id); +} + +SOKOL_API_IMPL void sgl_load_pipeline(sgl_pipeline pip_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->pip_tos >= 0) && (ctx->pip_tos < _SGL_MAX_STACK_DEPTH)); + ctx->pip_stack[ctx->pip_tos] = pip_id; +} + +SOKOL_API_IMPL void sgl_load_default_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->pip_tos >= 0) && (ctx->pip_tos < _SGL_MAX_STACK_DEPTH)); + ctx->pip_stack[ctx->pip_tos] = ctx->def_pip; +} + +SOKOL_API_IMPL void sgl_push_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + if (ctx->pip_tos < (_SGL_MAX_STACK_DEPTH - 1)) { + ctx->pip_tos++; + ctx->pip_stack[ctx->pip_tos] = ctx->pip_stack[ctx->pip_tos-1]; + } else { + ctx->error.stack_overflow = true; + ctx->error.any = true; + } +} + +SOKOL_API_IMPL void sgl_pop_pipeline(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + if (ctx->pip_tos > 0) { + ctx->pip_tos--; + } else { + ctx->error.stack_underflow = true; + ctx->error.any = true; + } +} + +SOKOL_API_IMPL void sgl_defaults(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->u = 0.0f; ctx->v = 0.0f; + ctx->rgba = 0xFFFFFFFF; + ctx->point_size = 1.0f; + ctx->texturing_enabled = false; + ctx->cur_img = _sgl.def_img; + ctx->cur_smp = _sgl.def_smp; + sgl_load_default_pipeline(); + _sgl_identity(_sgl_matrix_texture(ctx)); + _sgl_identity(_sgl_matrix_modelview(ctx)); + _sgl_identity(_sgl_matrix_projection(ctx)); + ctx->cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; + ctx->matrix_dirty = true; +} + +SOKOL_API_IMPL void sgl_layer(int layer_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->layer_id = layer_id; +} + +SOKOL_API_IMPL void sgl_viewport(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + cmd->cmd = SGL_COMMAND_VIEWPORT; + cmd->layer_id = ctx->layer_id; + cmd->args.viewport.x = x; + cmd->args.viewport.y = y; + cmd->args.viewport.w = w; + cmd->args.viewport.h = h; + cmd->args.viewport.origin_top_left = origin_top_left; + } +} + +SOKOL_API_IMPL void sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) { + sgl_viewport((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + cmd->cmd = SGL_COMMAND_SCISSOR_RECT; + cmd->layer_id = ctx->layer_id; + cmd->args.scissor_rect.x = x; + cmd->args.scissor_rect.y = y; + cmd->args.scissor_rect.w = w; + cmd->args.scissor_rect.h = h; + cmd->args.scissor_rect.origin_top_left = origin_top_left; + } +} + +SOKOL_API_IMPL void sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) { + sgl_scissor_rect((int)x, (int)y, (int)w, (int)h, origin_top_left); +} + +SOKOL_API_IMPL void sgl_enable_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->texturing_enabled = true; +} + +SOKOL_API_IMPL void sgl_disable_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + ctx->texturing_enabled = false; +} + +SOKOL_API_IMPL void sgl_texture(sg_image img, sg_sampler smp) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + if (SG_INVALID_ID != img.id) { + ctx->cur_img = img; + } else { + ctx->cur_img = _sgl.def_img; + } + if (SG_INVALID_ID != smp.id) { + ctx->cur_smp = smp; + } else { + ctx->cur_smp = _sgl.def_smp; + } +} + +SOKOL_API_IMPL void sgl_begin_points(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_POINTS); +} + +SOKOL_API_IMPL void sgl_begin_lines(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_LINES); +} + +SOKOL_API_IMPL void sgl_begin_line_strip(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_LINE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_triangles(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_TRIANGLES); +} + +SOKOL_API_IMPL void sgl_begin_triangle_strip(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_TRIANGLE_STRIP); +} + +SOKOL_API_IMPL void sgl_begin_quads(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(!ctx->in_begin); + _sgl_begin(ctx, SGL_PRIMITIVETYPE_QUADS); +} + +SOKOL_API_IMPL void sgl_end(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT(ctx->in_begin); + SOKOL_ASSERT(ctx->vertices.next >= ctx->base_vertex); + ctx->in_begin = false; + + bool matrix_dirty = ctx->matrix_dirty; + if (matrix_dirty) { + ctx->matrix_dirty = false; + _sgl_uniform_t* uni = _sgl_next_uniform(ctx); + if (uni) { + _sgl_matmul4(&uni->mvp, _sgl_matrix_projection(ctx), _sgl_matrix_modelview(ctx)); + uni->tm = *_sgl_matrix_texture(ctx); + } + } + + // don't record any new commands when we're in an error state + if (ctx->error.any) { + return; + } + + // check if command can be merged with current command + sg_pipeline pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type); + sg_image img = ctx->texturing_enabled ? ctx->cur_img : _sgl.def_img; + sg_sampler smp = ctx->texturing_enabled ? ctx->cur_smp : _sgl.def_smp; + _sgl_command_t* cur_cmd = _sgl_cur_command(ctx); + bool merge_cmd = false; + if (cur_cmd) { + if ((cur_cmd->cmd == SGL_COMMAND_DRAW) && + (cur_cmd->layer_id == ctx->layer_id) && + (ctx->cur_prim_type != SGL_PRIMITIVETYPE_LINE_STRIP) && + (ctx->cur_prim_type != SGL_PRIMITIVETYPE_TRIANGLE_STRIP) && + !matrix_dirty && + (cur_cmd->args.draw.img.id == img.id) && + (cur_cmd->args.draw.smp.id == smp.id) && + (cur_cmd->args.draw.pip.id == pip.id)) + { + merge_cmd = true; + } + } + if (merge_cmd) { + // draw command can be merged with the previous command + cur_cmd->args.draw.num_vertices += ctx->vertices.next - ctx->base_vertex; + } else { + // append a new draw command + _sgl_command_t* cmd = _sgl_next_command(ctx); + if (cmd) { + SOKOL_ASSERT(ctx->uniforms.next > 0); + cmd->cmd = SGL_COMMAND_DRAW; + cmd->layer_id = ctx->layer_id; + cmd->args.draw.img = img; + cmd->args.draw.smp = smp; + cmd->args.draw.pip = _sgl_get_pipeline(ctx->pip_stack[ctx->pip_tos], ctx->cur_prim_type); + cmd->args.draw.base_vertex = ctx->base_vertex; + cmd->args.draw.num_vertices = ctx->vertices.next - ctx->base_vertex; + cmd->args.draw.uniform_index = ctx->uniforms.next - 1; + } + } +} + +SOKOL_API_IMPL void sgl_point_size(float s) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->point_size = s; + } +} + +SOKOL_API_IMPL void sgl_t2f(float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->u = u; + ctx->v = v; + } +} + +SOKOL_API_IMPL void sgl_c3f(float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbaf(r, g, b, 1.0f); + } +} + +SOKOL_API_IMPL void sgl_c4f(float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbaf(r, g, b, a); + } +} + +SOKOL_API_IMPL void sgl_c3b(uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbab(r, g, b, 255); + } +} + +SOKOL_API_IMPL void sgl_c4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = _sgl_pack_rgbab(r, g, b, a); + } +} + +SOKOL_API_IMPL void sgl_c1i(uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->rgba = rgba; + } +} + +SOKOL_API_IMPL void sgl_v2f(float x, float y) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f(float x, float y, float z) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f(float x, float y, float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f(float x, float y, float z, float u, float v) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, ctx->rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_c3f(float x, float y, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c3b(float x, float y, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c4f(float x, float y, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c4b(float x, float y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_c1i(float x, float y, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, ctx->u, ctx->v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_c3f(float x, float y, float z, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c3b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c4f(float x, float y, float z, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c4b(float x, float y, float z, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_c1i(float x, float y, float z, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, ctx->u, ctx->v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3f(float x, float y, float u, float v, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c3b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4f(float x, float y, float u, float v, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c4b(float x, float y, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v2f_t2f_c1i(float x, float y, float u, float v, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, 0.0f, u, v, rgba); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3f(float x, float y, float z, float u, float v, float r, float g, float b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, 1.0f)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c3b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbab(r, g, b, 255)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4f(float x, float y, float z, float u, float v, float r, float g, float b, float a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbaf(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c4b(float x, float y, float z, float u, float v, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx, x, y, z, u, v, _sgl_pack_rgbab(r, g, b, a)); + } +} + +SOKOL_API_IMPL void sgl_v3f_t2f_c1i(float x, float y, float z, float u, float v, uint32_t rgba) { + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_vtx(ctx,x, y, z, u, v, rgba); + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_modelview(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_MODELVIEW; + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_projection(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_PROJECTION; + } +} + +SOKOL_API_IMPL void sgl_matrix_mode_texture(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + ctx->cur_matrix_mode = SGL_MATRIXMODE_TEXTURE; + } +} + +SOKOL_API_IMPL void sgl_load_identity(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_identity(_sgl_matrix(ctx)); +} + +SOKOL_API_IMPL void sgl_load_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + memcpy(&_sgl_matrix(ctx)->v[0][0], &m[0], 64); +} + +SOKOL_API_IMPL void sgl_load_transpose_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_transpose(_sgl_matrix(ctx), (const _sgl_matrix_t*) &m[0]); +} + +SOKOL_API_IMPL void sgl_mult_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + const _sgl_matrix_t* m0 = (const _sgl_matrix_t*) &m[0]; + _sgl_mul(_sgl_matrix(ctx), m0); +} + +SOKOL_API_IMPL void sgl_mult_transpose_matrix(const float m[16]) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_matrix_t m0; + _sgl_transpose(&m0, (const _sgl_matrix_t*) &m[0]); + _sgl_mul(_sgl_matrix(ctx), &m0); +} + +SOKOL_API_IMPL void sgl_rotate(float angle_rad, float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_rotate(_sgl_matrix(ctx), angle_rad, x, y, z); +} + +SOKOL_API_IMPL void sgl_scale(float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_scale(_sgl_matrix(ctx), x, y, z); +} + +SOKOL_API_IMPL void sgl_translate(float x, float y, float z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_translate(_sgl_matrix(ctx), x, y, z); +} + +SOKOL_API_IMPL void sgl_frustum(float l, float r, float b, float t, float n, float f) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_frustum(_sgl_matrix(ctx), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_ortho(float l, float r, float b, float t, float n, float f) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_ortho(_sgl_matrix(ctx), l, r, b, t, n, f); +} + +SOKOL_API_IMPL void sgl_perspective(float fov_y, float aspect, float z_near, float z_far) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_perspective(_sgl_matrix(ctx), fov_y, aspect, z_near, z_far); +} + +SOKOL_API_IMPL void sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + ctx->matrix_dirty = true; + _sgl_lookat(_sgl_matrix(ctx), eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z); +} + +SOKOL_GL_API_DECL void sgl_push_matrix(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->cur_matrix_mode >= 0) && (ctx->cur_matrix_mode < SGL_NUM_MATRIXMODES)); + ctx->matrix_dirty = true; + if (ctx->matrix_tos[ctx->cur_matrix_mode] < (_SGL_MAX_STACK_DEPTH - 1)) { + const _sgl_matrix_t* src = _sgl_matrix(ctx); + ctx->matrix_tos[ctx->cur_matrix_mode]++; + _sgl_matrix_t* dst = _sgl_matrix(ctx); + *dst = *src; + } else { + ctx->error.stack_overflow = true; + ctx->error.any = true; + } +} + +SOKOL_GL_API_DECL void sgl_pop_matrix(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (!ctx) { + return; + } + SOKOL_ASSERT((ctx->cur_matrix_mode >= 0) && (ctx->cur_matrix_mode < SGL_NUM_MATRIXMODES)); + ctx->matrix_dirty = true; + if (ctx->matrix_tos[ctx->cur_matrix_mode] > 0) { + ctx->matrix_tos[ctx->cur_matrix_mode]--; + } else { + ctx->error.stack_underflow = true; + ctx->error.any = true; + } +} + +SOKOL_API_IMPL void sgl_draw(void) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_draw(ctx, 0); + } +} + +SOKOL_API_IMPL void sgl_draw_layer(int layer_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl.cur_ctx; + if (ctx) { + _sgl_draw(ctx, layer_id); + } +} + +SOKOL_API_IMPL void sgl_context_draw(sgl_context ctx_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + _sgl_draw(ctx, 0); + } +} + +SOKOL_API_IMPL void sgl_context_draw_layer(sgl_context ctx_id, int layer_id) { + SOKOL_ASSERT(_SGL_INIT_COOKIE == _sgl.init_cookie); + _sgl_context_t* ctx = _sgl_lookup_context(ctx_id.id); + if (ctx) { + _sgl_draw(ctx, layer_id); + } +} + +#endif /* SOKOL_GL_IMPL */ diff --git a/modules/sokol-jai/sokol/c/sokol_glue.c b/modules/sokol-jai/sokol/c/sokol_glue.c new file mode 100644 index 0000000..33d23ce --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_glue.c @@ -0,0 +1,7 @@ +#if defined(IMPL) +#define SOKOL_GLUE_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "sokol_glue.h" diff --git a/modules/sokol-jai/sokol/c/sokol_glue.h b/modules/sokol-jai/sokol/c/sokol_glue.h new file mode 100644 index 0000000..a715b17 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_glue.h @@ -0,0 +1,162 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GLUE_IMPL) +#define SOKOL_GLUE_IMPL +#endif +#ifndef SOKOL_GLUE_INCLUDED +/* + sokol_glue.h -- glue helper functions for sokol headers + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GLUE_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_GLUE_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GLUE_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_glue.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GLUE_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + OVERVIEW + ======== + sokol_glue.h provides glue helper functions between sokol_gfx.h and sokol_app.h, + so that sokol_gfx.h doesn't need to depend on sokol_app.h but can be + used with different window system glue libraries. + + PROVIDED FUNCTIONS + ================== + + sg_environment sglue_environment(void) + + Returns an sg_environment struct initialized by calling sokol_app.h + functions. Use this in the sg_setup() call like this: + + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + ... + }); + + sg_swapchain sglue_swapchain(void) + + Returns an sg_swapchain struct initialized by calling sokol_app.h + functions. Use this in sg_begin_pass() for a 'swapchain pass' like + this: + + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain(), ... }); + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_GLUE_INCLUDED + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GLUE_API_DECL) +#define SOKOL_GLUE_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GLUE_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GLUE_IMPL) +#define SOKOL_GLUE_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GLUE_API_DECL __declspec(dllimport) +#else +#define SOKOL_GLUE_API_DECL extern +#endif +#endif + +#ifndef SOKOL_GFX_INCLUDED +#error "Please include sokol_gfx.h before sokol_glue.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_GLUE_API_DECL sg_environment sglue_environment(void); +SOKOL_GLUE_API_DECL sg_swapchain sglue_swapchain(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_GLUE_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_GLUE_IMPL +#define SOKOL_GLUE_IMPL_INCLUDED (1) +#include /* memset */ + +#ifndef SOKOL_APP_INCLUDED +#error "Please include sokol_app.h before the sokol_glue.h implementation" +#endif + +#ifndef SOKOL_API_IMPL +#define SOKOL_API_IMPL +#endif + + +SOKOL_API_IMPL sg_environment sglue_environment(void) { + sg_environment env; + memset(&env, 0, sizeof(env)); + env.defaults.color_format = (sg_pixel_format) sapp_color_format(); + env.defaults.depth_format = (sg_pixel_format) sapp_depth_format(); + env.defaults.sample_count = sapp_sample_count(); + env.metal.device = sapp_metal_get_device(); + env.d3d11.device = sapp_d3d11_get_device(); + env.d3d11.device_context = sapp_d3d11_get_device_context(); + env.wgpu.device = sapp_wgpu_get_device(); + return env; +} + +SOKOL_API_IMPL sg_swapchain sglue_swapchain(void) { + sg_swapchain swapchain; + memset(&swapchain, 0, sizeof(swapchain)); + swapchain.width = sapp_width(); + swapchain.height = sapp_height(); + swapchain.sample_count = sapp_sample_count(); + swapchain.color_format = (sg_pixel_format)sapp_color_format(); + swapchain.depth_format = (sg_pixel_format)sapp_depth_format(); + swapchain.metal.current_drawable = sapp_metal_get_current_drawable(); + swapchain.metal.depth_stencil_texture = sapp_metal_get_depth_stencil_texture(); + swapchain.metal.msaa_color_texture = sapp_metal_get_msaa_color_texture(); + swapchain.d3d11.render_view = sapp_d3d11_get_render_view(); + swapchain.d3d11.resolve_view = sapp_d3d11_get_resolve_view(); + swapchain.d3d11.depth_stencil_view = sapp_d3d11_get_depth_stencil_view(); + swapchain.wgpu.render_view = sapp_wgpu_get_render_view(); + swapchain.wgpu.resolve_view = sapp_wgpu_get_resolve_view(); + swapchain.wgpu.depth_stencil_view = sapp_wgpu_get_depth_stencil_view(); + swapchain.gl.framebuffer = sapp_gl_get_framebuffer(); + return swapchain; +} + +#endif /* SOKOL_GLUE_IMPL */ diff --git a/modules/sokol-jai/sokol/c/sokol_log.c b/modules/sokol-jai/sokol/c/sokol_log.c new file mode 100644 index 0000000..bfdc1da --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_log.c @@ -0,0 +1,5 @@ +#if defined(IMPL) +#define SOKOL_LOG_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_log.h" diff --git a/modules/sokol-jai/sokol/c/sokol_log.h b/modules/sokol-jai/sokol/c/sokol_log.h new file mode 100644 index 0000000..0eef3a9 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_log.h @@ -0,0 +1,334 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_LOG_IMPL) +#define SOKOL_LOG_IMPL +#endif +#ifndef SOKOL_LOG_INCLUDED +/* + sokol_log.h -- common logging callback for sokol headers + + Project URL: https://github.com/floooh/sokol + + Example code: https://github.com/floooh/sokol-samples + + Do this: + #define SOKOL_IMPL or + #define SOKOL_LOG_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines when building the implementation: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_LOG_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following for verbose output: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + + OVERVIEW + ======== + sokol_log.h provides a default logging callback for other sokol headers. + + To use the default log callback, just include sokol_log.h and provide + a function pointer to the 'slog_func' function when setting up the + sokol library: + + For instance with sokol_audio.h: + + #include "sokol_log.h" + ... + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + Logging output goes to stderr and/or a platform specific logging subsystem + (which means that in some scenarios you might see logging messages duplicated): + + - Windows: stderr + OutputDebugStringA() + - macOS/iOS/Linux: stderr + syslog() + - Emscripten: console.info()/warn()/error() + - Android: __android_log_write() + + On Windows with sokol_app.h also note the runtime config items to make + stdout/stderr output visible on the console for WinMain() applications + via sapp_desc.win32_console_attach or sapp_desc.win32_console_create, + however when running in a debugger on Windows, the logging output should + show up on the debug output UI panel. + + In debug mode, a log message might look like this: + + [sspine][error][id:12] /Users/floh/projects/sokol/util/sokol_spine.h:3472:0: + SKELETON_DESC_NO_ATLAS: no atlas object provided in sspine_skeleton_desc.atlas + + The source path and line number is formatted like compiler errors, in some IDEs (like VSCode) + such error messages are clickable. + + In release mode, logging is less verbose as to not bloat the executable with string data, but you still get + enough information to identify the type and location of an error: + + [sspine][error][id:12][line:3472] + + RULES FOR WRITING YOUR OWN LOGGING FUNCTION + =========================================== + - must be re-entrant because it might be called from different threads + - must treat **all** provided string pointers as optional (can be null) + - don't store the string pointers, copy the string data instead + - must not return for log level panic + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2023 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_LOG_INCLUDED (1) +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_LOG_API_DECL) +#define SOKOL_LOG_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_LOG_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_LOG_IMPL) +#define SOKOL_LOG_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_LOG_API_DECL __declspec(dllimport) +#else +#define SOKOL_LOG_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Plug this function into the 'logger.func' struct item when initializing any of the sokol + headers. For instance for sokol_audio.h it would look like this: + + saudio_setup(&(saudio_desc){ + .logger = { + .func = slog_func + } + }); +*/ +SOKOL_LOG_API_DECL void slog_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // SOKOL_LOG_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_LOG_IMPL +#define SOKOL_LOG_IMPL_INCLUDED (1) + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +// platform detection +#if defined(__APPLE__) + #define _SLOG_APPLE (1) +#elif defined(__EMSCRIPTEN__) + #define _SLOG_EMSCRIPTEN (1) +#elif defined(_WIN32) + #define _SLOG_WINDOWS (1) +#elif defined(__ANDROID__) + #define _SLOG_ANDROID (1) +#elif defined(__linux__) || defined(__unix__) + #define _SLOG_LINUX (1) +#else +#error "sokol_log.h: unknown platform" +#endif + +#include // abort +#include // fputs +#include // size_t + +#if defined(_SLOG_EMSCRIPTEN) +#include +#elif defined(_SLOG_WINDOWS) +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX + #define NOMINMAX +#endif +#include +#elif defined(_SLOG_ANDROID) +#include +#elif defined(_SLOG_LINUX) || defined(_SLOG_APPLE) +#include +#endif + +// size of line buffer (on stack!) in bytes including terminating zero +#define _SLOG_LINE_LENGTH (512) + +_SOKOL_PRIVATE char* _slog_append(const char* str, char* dst, char* end) { + if (str) { + char c; + while (((c = *str++) != 0) && (dst < (end - 1))) { + *dst++ = c; + } + } + *dst = 0; + return dst; +} + +_SOKOL_PRIVATE char* _slog_itoa(uint32_t x, char* buf, size_t buf_size) { + const size_t max_digits_and_null = 11; + if (buf_size < max_digits_and_null) { + return 0; + } + char* p = buf + max_digits_and_null; + *--p = 0; + do { + *--p = '0' + (x % 10); + x /= 10; + } while (x != 0); + return p; +} + +#if defined(_SLOG_EMSCRIPTEN) +EM_JS(void, slog_js_log, (uint32_t level, const char* c_str), { + const str = UTF8ToString(c_str); + switch (level) { + case 0: console.error(str); break; + case 1: console.error(str); break; + case 2: console.warn(str); break; + default: console.info(str); break; + } +}) +#endif + +SOKOL_API_IMPL void slog_func(const char* tag, uint32_t log_level, uint32_t log_item, const char* message, uint32_t line_nr, const char* filename, void* user_data) { + _SOKOL_UNUSED(user_data); + + const char* log_level_str; + switch (log_level) { + case 0: log_level_str = "panic"; break; + case 1: log_level_str = "error"; break; + case 2: log_level_str = "warning"; break; + default: log_level_str = "info"; break; + } + + // build log output line + char line_buf[_SLOG_LINE_LENGTH]; + char* str = line_buf; + char* end = line_buf + sizeof(line_buf); + char num_buf[32]; + if (tag) { + str = _slog_append("[", str, end); + str = _slog_append(tag, str, end); + str = _slog_append("]", str, end); + } + str = _slog_append("[", str, end); + str = _slog_append(log_level_str, str, end); + str = _slog_append("]", str, end); + str = _slog_append("[id:", str, end); + str = _slog_append(_slog_itoa(log_item, num_buf, sizeof(num_buf)), str, end); + str = _slog_append("]", str, end); + // if a filename is provided, build a clickable log message that's compatible with compiler error messages + if (filename) { + str = _slog_append(" ", str, end); + #if defined(_MSC_VER) + // MSVC compiler error format + str = _slog_append(filename, str, end); + str = _slog_append("(", str, end); + str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); + str = _slog_append("): ", str, end); + #else + // gcc/clang compiler error format + str = _slog_append(filename, str, end); + str = _slog_append(":", str, end); + str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); + str = _slog_append(":0: ", str, end); + #endif + } + else { + str = _slog_append("[line:", str, end); + str = _slog_append(_slog_itoa(line_nr, num_buf, sizeof(num_buf)), str, end); + str = _slog_append("] ", str, end); + } + if (message) { + str = _slog_append("\n\t", str, end); + str = _slog_append(message, str, end); + } + str = _slog_append("\n\n", str, end); + if (0 == log_level) { + str = _slog_append("ABORTING because of [panic]\n", str, end); + (void)str; + } + + // print to stderr? + #if defined(_SLOG_LINUX) || defined(_SLOG_WINDOWS) || defined(_SLOG_APPLE) + fputs(line_buf, stderr); + #endif + + // platform specific logging calls + #if defined(_SLOG_WINDOWS) + OutputDebugStringA(line_buf); + #elif defined(_SLOG_ANDROID) + int prio; + switch (log_level) { + case 0: prio = ANDROID_LOG_FATAL; break; + case 1: prio = ANDROID_LOG_ERROR; break; + case 2: prio = ANDROID_LOG_WARN; break; + default: prio = ANDROID_LOG_INFO; break; + } + __android_log_write(prio, "SOKOL", line_buf); + #elif defined(_SLOG_EMSCRIPTEN) + slog_js_log(log_level, line_buf); + #endif + if (0 == log_level) { + abort(); + } +} +#endif // SOKOL_LOG_IMPL diff --git a/modules/sokol-jai/sokol/c/sokol_shape.c b/modules/sokol-jai/sokol/c/sokol_shape.c new file mode 100644 index 0000000..73449f0 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_shape.c @@ -0,0 +1,6 @@ +#if defined(IMPL) +#define SOKOL_SHAPE_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_gfx.h" +#include "sokol_shape.h" diff --git a/modules/sokol-jai/sokol/c/sokol_shape.h b/modules/sokol-jai/sokol/c/sokol_shape.h new file mode 100644 index 0000000..d1676dd --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_shape.h @@ -0,0 +1,1431 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_SHAPE_IMPL) +#define SOKOL_SHAPE_IMPL +#endif +#ifndef SOKOL_SHAPE_INCLUDED +/* + sokol_shape.h -- create simple primitive shapes for sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_SHAPE_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Include the following headers before including sokol_shape.h: + + sokol_gfx.h + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_SHAPE_API_DECL- public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_SHAPE_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_shape.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_SHAPE_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + FEATURE OVERVIEW + ================ + sokol_shape.h creates vertices and indices for simple shapes and + builds structs which can be plugged into sokol-gfx resource + creation functions: + + The following shape types are supported: + + - plane + - cube + - sphere (with poles, not geodesic) + - cylinder + - torus (donut) + + Generated vertices look like this: + + typedef struct sshape_vertex_t { + float x, y, z; + uint32_t normal; // packed normal as BYTE4N + uint16_t u, v; // packed uv coords as USHORT2N + uint32_t color; // packed color as UBYTE4N (r,g,b,a); + } sshape_vertex_t; + + Indices are generally 16-bits wide (SG_INDEXTYPE_UINT16) and the indices + are written as triangle-lists (SG_PRIMITIVETYPE_TRIANGLES). + + EXAMPLES: + ========= + + Create multiple shapes into the same vertex- and index-buffer and + render with separate draw calls: + + https://github.com/floooh/sokol-samples/blob/master/sapp/shapes-sapp.c + + Same as the above, but pre-transform shapes and merge them into a single + shape that's rendered with a single draw call. + + https://github.com/floooh/sokol-samples/blob/master/sapp/shapes-transform-sapp.c + + STEP-BY-STEP: + ============= + + Setup an sshape_buffer_t struct with pointers to memory buffers where + generated vertices and indices will be written to: + + ```c + sshape_vertex_t vertices[512]; + uint16_t indices[4096]; + + sshape_buffer_t buf = { + .vertices = { + .buffer = SSHAPE_RANGE(vertices), + }, + .indices = { + .buffer = SSHAPE_RANGE(indices), + } + }; + ``` + + To find out how big those memory buffers must be (in case you want + to allocate dynamically) call the following functions: + + ```c + sshape_sizes_t sshape_plane_sizes(uint32_t tiles); + sshape_sizes_t sshape_box_sizes(uint32_t tiles); + sshape_sizes_t sshape_sphere_sizes(uint32_t slices, uint32_t stacks); + sshape_sizes_t sshape_cylinder_sizes(uint32_t slices, uint32_t stacks); + sshape_sizes_t sshape_torus_sizes(uint32_t sides, uint32_t rings); + ``` + + The returned sshape_sizes_t struct contains vertex- and index-counts + as well as the equivalent buffer sizes in bytes. For instance: + + ```c + sshape_sizes_t sizes = sshape_sphere_sizes(36, 12); + uint32_t num_vertices = sizes.vertices.num; + uint32_t num_indices = sizes.indices.num; + uint32_t vertex_buffer_size = sizes.vertices.size; + uint32_t index_buffer_size = sizes.indices.size; + ``` + + With the sshape_buffer_t struct that was setup earlier, call any + of the shape-builder functions: + + ```c + sshape_buffer_t sshape_build_plane(const sshape_buffer_t* buf, const sshape_plane_t* params); + sshape_buffer_t sshape_build_box(const sshape_buffer_t* buf, const sshape_box_t* params); + sshape_buffer_t sshape_build_sphere(const sshape_buffer_t* buf, const sshape_sphere_t* params); + sshape_buffer_t sshape_build_cylinder(const sshape_buffer_t* buf, const sshape_cylinder_t* params); + sshape_buffer_t sshape_build_torus(const sshape_buffer_t* buf, const sshape_torus_t* params); + ``` + + Note how the sshape_buffer_t struct is both an input value and the + return value. This can be used to append multiple shapes into the + same vertex- and index-buffers (more on this later). + + The second argument is a struct which holds creation parameters. + + For instance to build a sphere with radius 2, 36 "cake slices" and 12 stacks: + + ```c + sshape_buffer_t buf = ...; + buf = sshape_build_sphere(&buf, &(sshape_sphere_t){ + .radius = 2.0f, + .slices = 36, + .stacks = 12, + }); + ``` + + If the provided buffers are big enough to hold all generated vertices and + indices, the "valid" field in the result will be true: + + ```c + assert(buf.valid); + ``` + + The shape creation parameters have "useful defaults", refer to the + actual C struct declarations below to look up those defaults. + + You can also provide additional creation parameters, like a common vertex + color, a debug-helper to randomize colors, tell the shape builder function + to merge the new shape with the previous shape into the same draw-element-range, + or a 4x4 transform matrix to move, rotate and scale the generated vertices: + + ```c + sshape_buffer_t buf = ...; + buf = sshape_build_sphere(&buf, &(sshape_sphere_t){ + .radius = 2.0f, + .slices = 36, + .stacks = 12, + // merge with previous shape into a single element-range + .merge = true, + // set vertex color to red+opaque + .color = sshape_color_4f(1.0f, 0.0f, 0.0f, 1.0f), + // set position to y = 2.0 + .transform = { + .m = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 2.0f, 0.0f, 1.0f }, + } + } + }); + assert(buf.valid); + ``` + + The following helper functions can be used to build a packed + color value or to convert from external matrix types: + + ```c + uint32_t sshape_color_4f(float r, float g, float b, float a); + uint32_t sshape_color_3f(float r, float g, float b); + uint32_t sshape_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + uint32_t sshape_color_3b(uint8_t r, uint8_t g, uint8_t b); + sshape_mat4_t sshape_mat4(const float m[16]); + sshape_mat4_t sshape_mat4_transpose(const float m[16]); + ``` + + After the shape builder function has been called, the following functions + are used to extract the build result for plugging into sokol_gfx.h: + + ```c + sshape_element_range_t sshape_element_range(const sshape_buffer_t* buf); + sg_buffer_desc sshape_vertex_buffer_desc(const sshape_buffer_t* buf); + sg_buffer_desc sshape_index_buffer_desc(const sshape_buffer_t* buf); + sg_vertex_buffer_layout_state sshape_vertex_buffer_layout_state(void); + sg_vertex_attr_state sshape_position_vertex_attr_state(void); + sg_vertex_attr_state sshape_normal_vertex_attr_state(void); + sg_vertex_attr_state sshape_texcoord_vertex_attr_state(void); + sg_vertex_attr_state sshape_color_vertex_attr_state(void); + ``` + + The sshape_element_range_t struct contains the base-index and number of + indices which can be plugged into the sg_draw() call: + + ```c + sshape_element_range_t elms = sshape_element_range(&buf); + ... + sg_draw(elms.base_element, elms.num_elements, 1); + ``` + + To create sokol-gfx vertex- and index-buffers from the generated + shape data: + + ```c + // create sokol-gfx vertex buffer + sg_buffer_desc vbuf_desc = sshape_vertex_buffer_desc(&buf); + sg_buffer vbuf = sg_make_buffer(&vbuf_desc); + + // create sokol-gfx index buffer + sg_buffer_desc ibuf_desc = sshape_index_buffer_desc(&buf); + sg_buffer ibuf = sg_make_buffer(&ibuf_desc); + ``` + + The remaining functions are used to populate the vertex-layout item + in sg_pipeline_desc, note that these functions don't depend on the + created geometry, they always return the same result: + + ```c + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .buffers[0] = sshape_vertex_buffer_layout_state(), + .attrs = { + [0] = sshape_position_vertex_attr_state(), + [1] = ssape_normal_vertex_attr_state(), + [2] = sshape_texcoord_vertex_attr_state(), + [3] = sshape_color_vertex_attr_state() + } + }, + ... + }); + ``` + + Note that you don't have to use all generated vertex attributes in the + pipeline's vertex layout, the sg_vertex_buffer_layout_state struct returned + by sshape_vertex_buffer_layout_state() contains the correct vertex stride + to skip vertex components. + + WRITING MULTIPLE SHAPES INTO THE SAME BUFFER + ============================================ + You can merge multiple shapes into the same vertex- and + index-buffers and either render them as a single shape, or + in separate draw calls. + + To build a single shape made of two cubes which can be rendered + in a single draw-call: + + ``` + sshape_vertex_t vertices[128]; + uint16_t indices[16]; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vertices), + .indices.buffer = SSHAPE_RANGE(indices) + }; + + // first cube at pos x=-2.0 (with default size of 1x1x1) + buf = sshape_build_cube(&buf, &(sshape_box_t){ + .transform = { + .m = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + {-2.0f, 0.0f, 0.0f, 1.0f }, + } + } + }); + // ...and append another cube at pos pos=+1.0 + // NOTE the .merge = true, this tells the shape builder + // function to not advance the current shape start offset + buf = sshape_build_cube(&buf, &(sshape_box_t){ + .merge = true, + .transform = { + .m = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + {-2.0f, 0.0f, 0.0f, 1.0f }, + } + } + }); + assert(buf.valid); + + // skipping buffer- and pipeline-creation... + + sshape_element_range_t elms = sshape_element_range(&buf); + sg_draw(elms.base_element, elms.num_elements, 1); + ``` + + To render the two cubes in separate draw-calls, the element-ranges used + in the sg_draw() calls must be captured right after calling the + builder-functions: + + ```c + sshape_vertex_t vertices[128]; + uint16_t indices[16]; + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vertices), + .indices.buffer = SSHAPE_RANGE(indices) + }; + + // build a red cube... + buf = sshape_build_cube(&buf, &(sshape_box_t){ + .color = sshape_color_3b(255, 0, 0) + }); + sshape_element_range_t red_cube = sshape_element_range(&buf); + + // append a green cube to the same vertex-/index-buffer: + buf = sshape_build_cube(&bud, &sshape_box_t){ + .color = sshape_color_3b(0, 255, 0); + }); + sshape_element_range_t green_cube = sshape_element_range(&buf); + + // skipping buffer- and pipeline-creation... + + sg_draw(red_cube.base_element, red_cube.num_elements, 1); + sg_draw(green_cube.base_element, green_cube.num_elements, 1); + ``` + + ...that's about all :) + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2020 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_SHAPE_INCLUDED +#include // size_t, offsetof +#include +#include + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_shape.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_SHAPE_API_DECL) +#define SOKOL_SHAPE_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_SHAPE_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_SHAPE_IMPL) +#define SOKOL_SHAPE_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_SHAPE_API_DECL __declspec(dllimport) +#else +#define SOKOL_SHAPE_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sshape_range is a pointer-size-pair struct used to pass memory + blobs into sokol-shape. When initialized from a value type + (array or struct), use the SSHAPE_RANGE() macro to build + an sshape_range struct. +*/ +typedef struct sshape_range { + const void* ptr; + size_t size; +} sshape_range; + +// disabling this for every includer isn't great, but the warning is also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) /* /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' */ +#endif +#if defined(__cplusplus) +#define SSHAPE_RANGE(x) sshape_range{ &x, sizeof(x) } +#else +#define SSHAPE_RANGE(x) (sshape_range){ &x, sizeof(x) } +#endif + +/* a 4x4 matrix wrapper struct */ +typedef struct sshape_mat4_t { float m[4][4]; } sshape_mat4_t; + +/* vertex layout of the generated geometry */ +typedef struct sshape_vertex_t { + float x, y, z; + uint32_t normal; // packed normal as BYTE4N + uint16_t u, v; // packed uv coords as USHORT2N + uint32_t color; // packed color as UBYTE4N (r,g,b,a); +} sshape_vertex_t; + +/* a range of draw-elements (sg_draw(int base_element, int num_element, ...)) */ +typedef struct sshape_element_range_t { + int base_element; + int num_elements; +} sshape_element_range_t; + +/* number of elements and byte size of build actions */ +typedef struct sshape_sizes_item_t { + uint32_t num; // number of elements + uint32_t size; // the same as size in bytes +} sshape_sizes_item_t; + +typedef struct sshape_sizes_t { + sshape_sizes_item_t vertices; + sshape_sizes_item_t indices; +} sshape_sizes_t; + +/* in/out struct to keep track of mesh-build state */ +typedef struct sshape_buffer_item_t { + sshape_range buffer; // pointer/size pair of output buffer + size_t data_size; // size in bytes of valid data in buffer + size_t shape_offset; // data offset of the most recent shape +} sshape_buffer_item_t; + +typedef struct sshape_buffer_t { + bool valid; + sshape_buffer_item_t vertices; + sshape_buffer_item_t indices; +} sshape_buffer_t; + +/* creation parameters for the different shape types */ +typedef struct sshape_plane_t { + float width, depth; // default: 1.0 + uint16_t tiles; // default: 1 + uint32_t color; // default: white + bool random_colors; // default: false + bool merge; // if true merge with previous shape (default: false) + sshape_mat4_t transform; // default: identity matrix +} sshape_plane_t; + +typedef struct sshape_box_t { + float width, height, depth; // default: 1.0 + uint16_t tiles; // default: 1 + uint32_t color; // default: white + bool random_colors; // default: false + bool merge; // if true merge with previous shape (default: false) + sshape_mat4_t transform; // default: identity matrix +} sshape_box_t; + +typedef struct sshape_sphere_t { + float radius; // default: 0.5 + uint16_t slices; // default: 5 + uint16_t stacks; // default: 4 + uint32_t color; // default: white + bool random_colors; // default: false + bool merge; // if true merge with previous shape (default: false) + sshape_mat4_t transform; // default: identity matrix +} sshape_sphere_t; + +typedef struct sshape_cylinder_t { + float radius; // default: 0.5 + float height; // default: 1.0 + uint16_t slices; // default: 5 + uint16_t stacks; // default: 1 + uint32_t color; // default: white + bool random_colors; // default: false + bool merge; // if true merge with previous shape (default: false) + sshape_mat4_t transform; // default: identity matrix +} sshape_cylinder_t; + +typedef struct sshape_torus_t { + float radius; // default: 0.5f + float ring_radius; // default: 0.2f + uint16_t sides; // default: 5 + uint16_t rings; // default: 5 + uint32_t color; // default: white + bool random_colors; // default: false + bool merge; // if true merge with previous shape (default: false) + sshape_mat4_t transform; // default: identity matrix +} sshape_torus_t; + +/* shape builder functions */ +SOKOL_SHAPE_API_DECL sshape_buffer_t sshape_build_plane(const sshape_buffer_t* buf, const sshape_plane_t* params); +SOKOL_SHAPE_API_DECL sshape_buffer_t sshape_build_box(const sshape_buffer_t* buf, const sshape_box_t* params); +SOKOL_SHAPE_API_DECL sshape_buffer_t sshape_build_sphere(const sshape_buffer_t* buf, const sshape_sphere_t* params); +SOKOL_SHAPE_API_DECL sshape_buffer_t sshape_build_cylinder(const sshape_buffer_t* buf, const sshape_cylinder_t* params); +SOKOL_SHAPE_API_DECL sshape_buffer_t sshape_build_torus(const sshape_buffer_t* buf, const sshape_torus_t* params); + +/* query required vertex- and index-buffer sizes in bytes */ +SOKOL_SHAPE_API_DECL sshape_sizes_t sshape_plane_sizes(uint32_t tiles); +SOKOL_SHAPE_API_DECL sshape_sizes_t sshape_box_sizes(uint32_t tiles); +SOKOL_SHAPE_API_DECL sshape_sizes_t sshape_sphere_sizes(uint32_t slices, uint32_t stacks); +SOKOL_SHAPE_API_DECL sshape_sizes_t sshape_cylinder_sizes(uint32_t slices, uint32_t stacks); +SOKOL_SHAPE_API_DECL sshape_sizes_t sshape_torus_sizes(uint32_t sides, uint32_t rings); + +/* extract sokol-gfx desc structs and primitive ranges from build state */ +SOKOL_SHAPE_API_DECL sshape_element_range_t sshape_element_range(const sshape_buffer_t* buf); +SOKOL_SHAPE_API_DECL sg_buffer_desc sshape_vertex_buffer_desc(const sshape_buffer_t* buf); +SOKOL_SHAPE_API_DECL sg_buffer_desc sshape_index_buffer_desc(const sshape_buffer_t* buf); +SOKOL_SHAPE_API_DECL sg_vertex_buffer_layout_state sshape_vertex_buffer_layout_state(void); +SOKOL_SHAPE_API_DECL sg_vertex_attr_state sshape_position_vertex_attr_state(void); +SOKOL_SHAPE_API_DECL sg_vertex_attr_state sshape_normal_vertex_attr_state(void); +SOKOL_SHAPE_API_DECL sg_vertex_attr_state sshape_texcoord_vertex_attr_state(void); +SOKOL_SHAPE_API_DECL sg_vertex_attr_state sshape_color_vertex_attr_state(void); + +/* helper functions to build packed color value from floats or bytes */ +SOKOL_SHAPE_API_DECL uint32_t sshape_color_4f(float r, float g, float b, float a); +SOKOL_SHAPE_API_DECL uint32_t sshape_color_3f(float r, float g, float b); +SOKOL_SHAPE_API_DECL uint32_t sshape_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_SHAPE_API_DECL uint32_t sshape_color_3b(uint8_t r, uint8_t g, uint8_t b); + +/* adapter function for filling matrix struct from generic float[16] array */ +SOKOL_SHAPE_API_DECL sshape_mat4_t sshape_mat4(const float m[16]); +SOKOL_SHAPE_API_DECL sshape_mat4_t sshape_mat4_transpose(const float m[16]); + +#ifdef __cplusplus +} // extern "C" + +// FIXME: C++ helper functions + +#endif +#endif // SOKOL_SHAPE_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_SHAPE_IMPL +#define SOKOL_SHAPE_IMPL_INCLUDED (1) + +#include // memcpy +#include // sinf, cosf + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#define _sshape_def(val, def) (((val) == 0) ? (def) : (val)) +#define _sshape_def_flt(val, def) (((val) == 0.0f) ? (def) : (val)) +#define _sshape_white (0xFFFFFFFF) + +typedef struct { float x, y, z, w; } _sshape_vec4_t; +typedef struct { float x, y; } _sshape_vec2_t; + +static inline float _sshape_clamp(float v) { + if (v < 0.0f) return 0.0f; + else if (v > 1.0f) return 1.0f; + else return v; +} + +static inline uint32_t _sshape_pack_ub4_ubyte4n(uint8_t x, uint8_t y, uint8_t z, uint8_t w) { + return (uint32_t)(((uint32_t)w<<24)|((uint32_t)z<<16)|((uint32_t)y<<8)|x); +} + +static inline uint32_t _sshape_pack_f4_ubyte4n(float x, float y, float z, float w) { + uint8_t x8 = (uint8_t) (x * 255.0f); + uint8_t y8 = (uint8_t) (y * 255.0f); + uint8_t z8 = (uint8_t) (z * 255.0f); + uint8_t w8 = (uint8_t) (w * 255.0f); + return _sshape_pack_ub4_ubyte4n(x8, y8, z8, w8); +} + +static inline uint32_t _sshape_pack_f4_byte4n(float x, float y, float z, float w) { + int8_t x8 = (int8_t) (x * 127.0f); + int8_t y8 = (int8_t) (y * 127.0f); + int8_t z8 = (int8_t) (z * 127.0f); + int8_t w8 = (int8_t) (w * 127.0f); + return _sshape_pack_ub4_ubyte4n((uint8_t)x8, (uint8_t)y8, (uint8_t)z8, (uint8_t)w8); +} + +static inline uint16_t _sshape_pack_f_ushortn(float x) { + return (uint16_t) (x * 65535.0f); +} + +static inline _sshape_vec4_t _sshape_vec4(float x, float y, float z, float w) { + _sshape_vec4_t v = { x, y, z, w }; + return v; +} + +static inline _sshape_vec4_t _sshape_vec4_norm(_sshape_vec4_t v) { + float l = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w); + if (l != 0.0f) { + return _sshape_vec4(v.x/l, v.y/l, v.z/l, v.w/l); + } + else { + return _sshape_vec4(0.0f, 1.0f, 0.0f, 0.0f); + } +} + +static inline _sshape_vec2_t _sshape_vec2(float x, float y) { + _sshape_vec2_t v = { x, y }; + return v; +} + +static bool _sshape_mat4_isnull(const sshape_mat4_t* m) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (0.0f != m->m[y][x]) { + return false; + } + } + } + return true; +} + +static sshape_mat4_t _sshape_mat4_identity(void) { + sshape_mat4_t m = { + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + return m; +} + +static _sshape_vec4_t _sshape_mat4_mul(const sshape_mat4_t* m, _sshape_vec4_t v) { + _sshape_vec4_t res = { + m->m[0][0]*v.x + m->m[1][0]*v.y + m->m[2][0]*v.z + m->m[3][0]*v.w, + m->m[0][1]*v.x + m->m[1][1]*v.y + m->m[2][1]*v.z + m->m[3][1]*v.w, + m->m[0][2]*v.x + m->m[1][2]*v.y + m->m[2][2]*v.z + m->m[3][2]*v.w, + m->m[0][3]*v.x + m->m[1][3]*v.y + m->m[2][3]*v.z + m->m[3][3]*v.w + }; + return res; +} + +static uint32_t _sshape_plane_num_vertices(uint32_t tiles) { + return (tiles + 1) * (tiles + 1); +} + +static uint32_t _sshape_plane_num_indices(uint32_t tiles) { + return tiles * tiles * 2 * 3; +} + +static uint32_t _sshape_box_num_vertices(uint32_t tiles) { + return (tiles + 1) * (tiles + 1) * 6; +} + +static uint32_t _sshape_box_num_indices(uint32_t tiles) { + return tiles * tiles * 2 * 6 * 3; +} + +static uint32_t _sshape_sphere_num_vertices(uint32_t slices, uint32_t stacks) { + return (slices + 1) * (stacks + 1); +} + +static uint32_t _sshape_sphere_num_indices(uint32_t slices, uint32_t stacks) { + return ((2 * slices * stacks) - (2 * slices)) * 3; +} + +static uint32_t _sshape_cylinder_num_vertices(uint32_t slices, uint32_t stacks) { + return (slices + 1) * (stacks + 5); +} + +static uint32_t _sshape_cylinder_num_indices(uint32_t slices, uint32_t stacks) { + return ((2 * slices * stacks) + (2 * slices)) * 3; +} + +static uint32_t _sshape_torus_num_vertices(uint32_t sides, uint32_t rings) { + return (sides + 1) * (rings + 1); +} + +static uint32_t _sshape_torus_num_indices(uint32_t sides, uint32_t rings) { + return sides * rings * 2 * 3; +} + +static bool _sshape_validate_buffer_item(const sshape_buffer_item_t* item, uint32_t build_size) { + if (0 == item->buffer.ptr) { + return false; + } + if (0 == item->buffer.size) { + return false; + } + if ((item->data_size + build_size) > item->buffer.size) { + return false; + } + if (item->shape_offset > item->data_size) { + return false; + } + return true; +} + +static bool _sshape_validate_buffer(const sshape_buffer_t* buf, uint32_t num_vertices, uint32_t num_indices) { + if (!_sshape_validate_buffer_item(&buf->vertices, num_vertices * sizeof(sshape_vertex_t))) { + return false; + } + if (!_sshape_validate_buffer_item(&buf->indices, num_indices * sizeof(uint16_t))) { + return false; + } + return true; +} + +static void _sshape_advance_offset(sshape_buffer_item_t* item) { + item->shape_offset = item->data_size; +} + +static uint16_t _sshape_base_index(const sshape_buffer_t* buf) { + return (uint16_t) (buf->vertices.data_size / sizeof(sshape_vertex_t)); +} + +static sshape_plane_t _sshape_plane_defaults(const sshape_plane_t* params) { + sshape_plane_t res = *params; + res.width = _sshape_def_flt(res.width, 1.0f); + res.depth = _sshape_def_flt(res.depth, 1.0f); + res.tiles = _sshape_def(res.tiles, 1); + res.color = _sshape_def(res.color, _sshape_white); + res.transform = _sshape_mat4_isnull(&res.transform) ? _sshape_mat4_identity() : res.transform; + return res; +} + +static sshape_box_t _sshape_box_defaults(const sshape_box_t* params) { + sshape_box_t res = *params; + res.width = _sshape_def_flt(res.width, 1.0f); + res.height = _sshape_def_flt(res.height, 1.0f); + res.depth = _sshape_def_flt(res.depth, 1.0f); + res.tiles = _sshape_def(res.tiles, 1); + res.color = _sshape_def(res.color, _sshape_white); + res.transform = _sshape_mat4_isnull(&res.transform) ? _sshape_mat4_identity() : res.transform; + return res; +} + +static sshape_sphere_t _sshape_sphere_defaults(const sshape_sphere_t* params) { + sshape_sphere_t res = *params; + res.radius = _sshape_def_flt(res.radius, 0.5f); + res.slices = _sshape_def(res.slices, 5); + res.stacks = _sshape_def(res.stacks, 4); + res.color = _sshape_def(res.color, _sshape_white); + res.transform = _sshape_mat4_isnull(&res.transform) ? _sshape_mat4_identity() : res.transform; + return res; +} + +static sshape_cylinder_t _sshape_cylinder_defaults(const sshape_cylinder_t* params) { + sshape_cylinder_t res = *params; + res.radius = _sshape_def_flt(res.radius, 0.5f); + res.height = _sshape_def_flt(res.height, 1.0f); + res.slices = _sshape_def(res.slices, 5); + res.stacks = _sshape_def(res.stacks, 1); + res.color = _sshape_def(res.color, _sshape_white); + res.transform = _sshape_mat4_isnull(&res.transform) ? _sshape_mat4_identity() : res.transform; + return res; +} + +static sshape_torus_t _sshape_torus_defaults(const sshape_torus_t* params) { + sshape_torus_t res = *params; + res.radius = _sshape_def_flt(res.radius, 0.5f); + res.ring_radius = _sshape_def_flt(res.ring_radius, 0.2f); + res.sides = _sshape_def_flt(res.sides, 5); + res.rings = _sshape_def_flt(res.rings, 5); + res.color = _sshape_def(res.color, _sshape_white); + res.transform = _sshape_mat4_isnull(&res.transform) ? _sshape_mat4_identity() : res.transform; + return res; +} + +static void _sshape_add_vertex(sshape_buffer_t* buf, _sshape_vec4_t pos, _sshape_vec4_t norm, _sshape_vec2_t uv, uint32_t color) { + size_t offset = buf->vertices.data_size; + SOKOL_ASSERT((offset + sizeof(sshape_vertex_t)) <= buf->vertices.buffer.size); + buf->vertices.data_size += sizeof(sshape_vertex_t); + sshape_vertex_t* v_ptr = (sshape_vertex_t*) ((uint8_t*)buf->vertices.buffer.ptr + offset); + v_ptr->x = pos.x; + v_ptr->y = pos.y; + v_ptr->z = pos.z; + v_ptr->normal = _sshape_pack_f4_byte4n(norm.x, norm.y, norm.z, norm.w); + v_ptr->u = _sshape_pack_f_ushortn(uv.x); + v_ptr->v = _sshape_pack_f_ushortn(uv.y); + v_ptr->color = color; +} + +static void _sshape_add_triangle(sshape_buffer_t* buf, uint16_t i0, uint16_t i1, uint16_t i2) { + size_t offset = buf->indices.data_size; + SOKOL_ASSERT((offset + 3*sizeof(uint16_t)) <= buf->indices.buffer.size); + buf->indices.data_size += 3*sizeof(uint16_t); + uint16_t* i_ptr = (uint16_t*) ((uint8_t*)buf->indices.buffer.ptr + offset); + i_ptr[0] = i0; + i_ptr[1] = i1; + i_ptr[2] = i2; +} + +static uint32_t _sshape_rand_color(uint32_t* xorshift_state) { + // xorshift32 + uint32_t x = *xorshift_state; + x ^= x<<13; + x ^= x>>17; + x ^= x<<5; + *xorshift_state = x; + + // rand => bright color with alpha 1.0 + x |= 0xFF000000; + return x; + +} + +/*=== PUBLIC API FUNCTIONS ===================================================*/ +SOKOL_API_IMPL uint32_t sshape_color_4f(float r, float g, float b, float a) { + return _sshape_pack_f4_ubyte4n(_sshape_clamp(r), _sshape_clamp(g), _sshape_clamp(b), _sshape_clamp(a)); +} + +SOKOL_API_IMPL uint32_t sshape_color_3f(float r, float g, float b) { + return _sshape_pack_f4_ubyte4n(_sshape_clamp(r), _sshape_clamp(g), _sshape_clamp(b), 1.0f); +} + +SOKOL_API_IMPL uint32_t sshape_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return _sshape_pack_ub4_ubyte4n(r, g, b, a); +} + +SOKOL_API_IMPL uint32_t sshape_color_3b(uint8_t r, uint8_t g, uint8_t b) { + return _sshape_pack_ub4_ubyte4n(r, g, b, 255); +} + +SOKOL_API_IMPL sshape_mat4_t sshape_mat4(const float m[16]) { + sshape_mat4_t res; + memcpy(&res.m[0][0], &m[0], 64); + return res; +} + +SOKOL_API_IMPL sshape_mat4_t sshape_mat4_transpose(const float m[16]) { + sshape_mat4_t res; + for (int c = 0; c < 4; c++) { + for (int r = 0; r < 4; r++) { + res.m[r][c] = m[c*4 + r]; + } + } + return res; +} + +SOKOL_API_IMPL sshape_sizes_t sshape_plane_sizes(uint32_t tiles) { + SOKOL_ASSERT(tiles >= 1); + sshape_sizes_t res = { {0} }; + res.vertices.num = _sshape_plane_num_vertices(tiles); + res.indices.num = _sshape_plane_num_indices(tiles); + res.vertices.size = res.vertices.num * sizeof(sshape_vertex_t); + res.indices.size = res.indices.num * sizeof(uint16_t); + return res; +} + +SOKOL_API_IMPL sshape_sizes_t sshape_box_sizes(uint32_t tiles) { + SOKOL_ASSERT(tiles >= 1); + sshape_sizes_t res = { {0} }; + res.vertices.num = _sshape_box_num_vertices(tiles); + res.indices.num = _sshape_box_num_indices(tiles); + res.vertices.size = res.vertices.num * sizeof(sshape_vertex_t); + res.indices.size = res.indices.num * sizeof(uint16_t); + return res; +} + +SOKOL_API_IMPL sshape_sizes_t sshape_sphere_sizes(uint32_t slices, uint32_t stacks) { + SOKOL_ASSERT((slices >= 3) && (stacks >= 2)); + sshape_sizes_t res = { {0} }; + res.vertices.num = _sshape_sphere_num_vertices(slices, stacks); + res.indices.num = _sshape_sphere_num_indices(slices, stacks); + res.vertices.size = res.vertices.num * sizeof(sshape_vertex_t); + res.indices.size = res.indices.num * sizeof(uint16_t); + return res; +} + +SOKOL_API_IMPL sshape_sizes_t sshape_cylinder_sizes(uint32_t slices, uint32_t stacks) { + SOKOL_ASSERT((slices >= 3) && (stacks >= 1)); + sshape_sizes_t res = { {0} }; + res.vertices.num = _sshape_cylinder_num_vertices(slices, stacks); + res.indices.num = _sshape_cylinder_num_indices(slices, stacks); + res.vertices.size = res.vertices.num * sizeof(sshape_vertex_t); + res.indices.size = res.indices.num * sizeof(uint16_t); + return res; +} + +SOKOL_API_IMPL sshape_sizes_t sshape_torus_sizes(uint32_t sides, uint32_t rings) { + SOKOL_ASSERT((sides >= 3) && (rings >= 3)); + sshape_sizes_t res = { {0} }; + res.vertices.num = _sshape_torus_num_vertices(sides, rings); + res.indices.num = _sshape_torus_num_indices(sides, rings); + res.vertices.size = res.vertices.num * sizeof(sshape_vertex_t); + res.indices.size = res.indices.num * sizeof(uint16_t); + return res; +} + +/* + Geometry layout for plane (4 tiles): + +--+--+--+--+ + |\ |\ |\ |\ | + | \| \| \| \| + +--+--+--+--+ 25 vertices (tiles + 1) * (tiles + 1) + |\ |\ |\ |\ | 32 triangles (tiles + 1) * (tiles + 1) * 2 + | \| \| \| \| + +--+--+--+--+ + |\ |\ |\ |\ | + | \| \| \| \| + +--+--+--+--+ + |\ |\ |\ |\ | + | \| \| \| \| + +--+--+--+--+ +*/ +SOKOL_API_IMPL sshape_buffer_t sshape_build_plane(const sshape_buffer_t* in_buf, const sshape_plane_t* in_params) { + SOKOL_ASSERT(in_buf && in_params); + const sshape_plane_t params = _sshape_plane_defaults(in_params); + const uint32_t num_vertices = _sshape_plane_num_vertices(params.tiles); + const uint32_t num_indices = _sshape_plane_num_indices(params.tiles); + sshape_buffer_t buf = *in_buf; + if (!_sshape_validate_buffer(&buf, num_vertices, num_indices)) { + buf.valid = false; + return buf; + } + buf.valid = true; + const uint16_t start_index = _sshape_base_index(&buf); + if (!params.merge) { + _sshape_advance_offset(&buf.vertices); + _sshape_advance_offset(&buf.indices); + } + + // write vertices + uint32_t rand_seed = 0x12345678; + const float x0 = -params.width * 0.5f; + const float z0 = params.depth * 0.5f; + const float dx = params.width / params.tiles; + const float dz = -params.depth / params.tiles; + const float duv = 1.0f / params.tiles; + _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, _sshape_vec4(0.0f, 1.0f, 0.0f, 0.0f))); + for (uint32_t ix = 0; ix <= params.tiles; ix++) { + for (uint32_t iz = 0; iz <= params.tiles; iz++) { + const _sshape_vec4_t pos = _sshape_vec4(x0 + dx*ix, 0.0f, z0 + dz*iz, 1.0f); + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec2_t uv = _sshape_vec2(duv*ix, duv*iz); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + + // write indices + for (uint16_t j = 0; j < params.tiles; j++) { + for (uint16_t i = 0; i < params.tiles; i++) { + const uint16_t i0 = start_index + (j * (params.tiles + 1)) + i; + const uint16_t i1 = i0 + 1; + const uint16_t i2 = i0 + params.tiles + 1; + const uint16_t i3 = i2 + 1; + _sshape_add_triangle(&buf, i0, i1, i3); + _sshape_add_triangle(&buf, i0, i3, i2); + } + } + return buf; +} + +SOKOL_API_IMPL sshape_buffer_t sshape_build_box(const sshape_buffer_t* in_buf, const sshape_box_t* in_params) { + SOKOL_ASSERT(in_buf && in_params); + const sshape_box_t params = _sshape_box_defaults(in_params); + const uint32_t num_vertices = _sshape_box_num_vertices(params.tiles); + const uint32_t num_indices = _sshape_box_num_indices(params.tiles); + sshape_buffer_t buf = *in_buf; + if (!_sshape_validate_buffer(&buf, num_vertices, num_indices)) { + buf.valid = false; + return buf; + } + buf.valid = true; + const uint16_t start_index = _sshape_base_index(&buf); + if (!params.merge) { + _sshape_advance_offset(&buf.vertices); + _sshape_advance_offset(&buf.indices); + } + + // build vertices + uint32_t rand_seed = 0x12345678; + const float x0 = -params.width * 0.5f; + const float x1 = params.width * 0.5f; + const float y0 = -params.height * 0.5f; + const float y1 = params.height * 0.5f; + const float z0 = -params.depth * 0.5f; + const float z1 = params.depth * 0.5f; + const float dx = params.width / params.tiles; + const float dy = params.height / params.tiles; + const float dz = params.depth / params.tiles; + const float duv = 1.0f / params.tiles; + + // bottom/top vertices + for (uint32_t top_bottom = 0; top_bottom < 2; top_bottom++) { + _sshape_vec4_t pos = _sshape_vec4(0.0f, (0==top_bottom) ? y0:y1, 0.0f, 1.0f); + const _sshape_vec4_t norm = _sshape_vec4(0.0f, (0==top_bottom) ? -1.0f:1.0f, 0.0f, 0.0f); + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, norm)); + for (uint32_t ix = 0; ix <= params.tiles; ix++) { + pos.x = (0==top_bottom) ? (x0 + dx * ix) : (x1 - dx * ix); + for (uint32_t iz = 0; iz <= params.tiles; iz++) { + pos.z = z0 + dz * iz; + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec2_t uv = _sshape_vec2(ix * duv, iz * duv); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + } + + // left/right vertices + for (uint32_t left_right = 0; left_right < 2; left_right++) { + _sshape_vec4_t pos = _sshape_vec4((0==left_right) ? x0:x1, 0.0f, 0.0f, 1.0f); + const _sshape_vec4_t norm = _sshape_vec4((0==left_right) ? -1.0f:1.0f, 0.0f, 0.0f, 0.0f); + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, norm)); + for (uint32_t iy = 0; iy <= params.tiles; iy++) { + pos.y = (0==left_right) ? (y1 - dy * iy) : (y0 + dy * iy); + for (uint32_t iz = 0; iz <= params.tiles; iz++) { + pos.z = z0 + dz * iz; + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec2_t uv = _sshape_vec2(iy * duv, iz * duv); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + } + + // front/back vertices + for (uint32_t front_back = 0; front_back < 2; front_back++) { + _sshape_vec4_t pos = _sshape_vec4(0.0f, 0.0f, (0==front_back) ? z0:z1, 1.0f); + const _sshape_vec4_t norm = _sshape_vec4(0.0f, 0.0f, (0==front_back) ? -1.0f:1.0f, 0.0f); + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, norm)); + for (uint32_t ix = 0; ix <= params.tiles; ix++) { + pos.x = (0==front_back) ? (x1 - dx * ix) : (x0 + dx * ix); + for (uint32_t iy = 0; iy <= params.tiles; iy++) { + pos.y = y0 + dy * iy; + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec2_t uv = _sshape_vec2(ix * duv, iy * duv); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + } + + // build indices + const uint16_t verts_per_face = (params.tiles + 1) * (params.tiles + 1); + for (uint16_t face = 0; face < 6; face++) { + uint16_t face_start_index = start_index + face * verts_per_face; + for (uint16_t j = 0; j < params.tiles; j++) { + for (uint16_t i = 0; i < params.tiles; i++) { + const uint16_t i0 = face_start_index + (j * (params.tiles + 1)) + i; + const uint16_t i1 = i0 + 1; + const uint16_t i2 = i0 + params.tiles + 1; + const uint16_t i3 = i2 + 1; + _sshape_add_triangle(&buf, i0, i1, i3); + _sshape_add_triangle(&buf, i0, i3, i2); + } + } + } + return buf; +} + +/* + Geometry layout for spheres is as follows (for 5 slices, 4 stacks): + + + + + + + + north pole + |\ |\ |\ |\ |\ + | \| \| \| \| \ + +--+--+--+--+--+ 30 vertices (slices + 1) * (stacks + 1) + |\ |\ |\ |\ |\ | 30 triangles (2 * slices * stacks) - (2 * slices) + | \| \| \| \| \| 2 orphaned vertices + +--+--+--+--+--+ + |\ |\ |\ |\ |\ | + | \| \| \| \| \| + +--+--+--+--+--+ + \ |\ |\ |\ |\ | + \| \| \| \| \| + + + + + + + south pole +*/ +SOKOL_API_IMPL sshape_buffer_t sshape_build_sphere(const sshape_buffer_t* in_buf, const sshape_sphere_t* in_params) { + SOKOL_ASSERT(in_buf && in_params); + const sshape_sphere_t params = _sshape_sphere_defaults(in_params); + const uint32_t num_vertices = _sshape_sphere_num_vertices(params.slices, params.stacks); + const uint32_t num_indices = _sshape_sphere_num_indices(params.slices, params.stacks); + sshape_buffer_t buf = *in_buf; + if (!_sshape_validate_buffer(&buf, num_vertices, num_indices)) { + buf.valid = false; + return buf; + } + buf.valid = true; + const uint16_t start_index = _sshape_base_index(&buf); + if (!params.merge) { + _sshape_advance_offset(&buf.vertices); + _sshape_advance_offset(&buf.indices); + } + + uint32_t rand_seed = 0x12345678; + const float pi = 3.14159265358979323846f; + const float two_pi = 2.0f * pi; + const float du = 1.0f / params.slices; + const float dv = 1.0f / params.stacks; + + // generate vertices + for (uint32_t stack = 0; stack <= params.stacks; stack++) { + const float stack_angle = (pi * stack) / params.stacks; + const float sin_stack = sinf(stack_angle); + const float cos_stack = cosf(stack_angle); + for (uint32_t slice = 0; slice <= params.slices; slice++) { + const float slice_angle = (two_pi * slice) / params.slices; + const float sin_slice = sinf(slice_angle); + const float cos_slice = cosf(slice_angle); + const _sshape_vec4_t norm = _sshape_vec4(-sin_slice * sin_stack, cos_stack, cos_slice * sin_stack, 0.0f); + const _sshape_vec4_t pos = _sshape_vec4(norm.x * params.radius, norm.y * params.radius, norm.z * params.radius, 1.0f); + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, norm)); + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec2_t uv = _sshape_vec2(1.0f - slice * du, 1.0f - stack * dv); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + + // generate indices + { + // north-pole triangles + const uint16_t row_a = start_index; + const uint16_t row_b = row_a + params.slices + 1; + for (uint16_t slice = 0; slice < params.slices; slice++) { + _sshape_add_triangle(&buf, row_a + slice, row_b + slice, row_b + slice + 1); + } + } + // stack triangles + for (uint16_t stack = 1; stack < params.stacks - 1; stack++) { + const uint16_t row_a = start_index + stack * (params.slices + 1); + const uint16_t row_b = row_a + params.slices + 1; + for (uint16_t slice = 0; slice < params.slices; slice++) { + _sshape_add_triangle(&buf, row_a + slice, row_b + slice + 1, row_a + slice + 1); + _sshape_add_triangle(&buf, row_a + slice, row_b + slice, row_b + slice + 1); + } + } + { + // south-pole triangles + const uint16_t row_a = start_index + (params.stacks - 1) * (params.slices + 1); + const uint16_t row_b = row_a + params.slices + 1; + for (uint16_t slice = 0; slice < params.slices; slice++) { + _sshape_add_triangle(&buf, row_a + slice, row_b + slice + 1, row_a + slice + 1); + } + } + return buf; +} + +/* + Geometry for cylinders is as follows (2 stacks, 5 slices): + + + + + + + + + |\ |\ |\ |\ |\ + | \| \| \| \| \ + +--+--+--+--+--+ + +--+--+--+--+--+ 42 vertices (2 wasted) (slices + 1) * (stacks + 5) + |\ |\ |\ |\ |\ | 30 triangles (2 * slices * stacks) + (2 * slices) + | \| \| \| \| \| + +--+--+--+--+--+ + |\ |\ |\ |\ |\ | + | \| \| \| \| \| + +--+--+--+--+--+ + +--+--+--+--+--+ + \ |\ |\ |\ |\ | + \| \| \| \| \| + + + + + + + +*/ +static void _sshape_build_cylinder_cap_pole(sshape_buffer_t* buf, const sshape_cylinder_t* params, float pos_y, float norm_y, float du, float v, uint32_t* rand_seed) { + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms->transform, _sshape_vec4(0.0f, norm_y, 0.0f, 0.0f))); + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms->transform, _sshape_vec4(0.0f, pos_y, 0.0f, 1.0f)); + for (uint32_t slice = 0; slice <= params->slices; slice++) { + const _sshape_vec2_t uv = _sshape_vec2(slice * du, 1.0f - v); + const uint32_t color = params->random_colors ? _sshape_rand_color(rand_seed) : params->color; + _sshape_add_vertex(buf, tpos, tnorm, uv, color); + } +} + +static void _sshape_build_cylinder_cap_ring(sshape_buffer_t* buf, const sshape_cylinder_t* params, float pos_y, float norm_y, float du, float v, uint32_t* rand_seed) { + const float two_pi = 2.0f * 3.14159265358979323846f; + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms->transform, _sshape_vec4(0.0f, norm_y, 0.0f, 0.0f))); + for (uint32_t slice = 0; slice <= params->slices; slice++) { + const float slice_angle = (two_pi * slice) / params->slices; + const float sin_slice = sinf(slice_angle); + const float cos_slice = cosf(slice_angle); + const _sshape_vec4_t pos = _sshape_vec4(sin_slice * params->radius, pos_y, cos_slice * params->radius, 1.0f); + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms->transform, pos); + const _sshape_vec2_t uv = _sshape_vec2(slice * du, 1.0f - v); + const uint32_t color = params->random_colors ? _sshape_rand_color(rand_seed) : params->color; + _sshape_add_vertex(buf, tpos, tnorm, uv, color); + } +} + +SOKOL_SHAPE_API_DECL sshape_buffer_t sshape_build_cylinder(const sshape_buffer_t* in_buf, const sshape_cylinder_t* in_params) { + SOKOL_ASSERT(in_buf && in_params); + const sshape_cylinder_t params = _sshape_cylinder_defaults(in_params); + const uint32_t num_vertices = _sshape_cylinder_num_vertices(params.slices, params.stacks); + const uint32_t num_indices = _sshape_cylinder_num_indices(params.slices, params.stacks); + sshape_buffer_t buf = *in_buf; + if (!_sshape_validate_buffer(&buf, num_vertices, num_indices)) { + buf.valid = false; + return buf; + } + buf.valid = true; + const uint16_t start_index = _sshape_base_index(&buf); + if (!params.merge) { + _sshape_advance_offset(&buf.vertices); + _sshape_advance_offset(&buf.indices); + } + + uint32_t rand_seed = 0x12345678; + const float two_pi = 2.0f * 3.14159265358979323846f; + const float du = 1.0f / params.slices; + const float dv = 1.0f / (params.stacks + 2); + const float y0 = params.height * 0.5f; + const float y1 = -params.height * 0.5f; + const float dy = params.height / params.stacks; + + // generate vertices + _sshape_build_cylinder_cap_pole(&buf, ¶ms, y0, 1.0f, du, 0.0f, &rand_seed); + _sshape_build_cylinder_cap_ring(&buf, ¶ms, y0, 1.0f, du, dv, &rand_seed); + for (uint32_t stack = 0; stack <= params.stacks; stack++) { + const float y = y0 - dy * stack; + const float v = dv * stack + dv; + for (uint32_t slice = 0; slice <= params.slices; slice++) { + const float slice_angle = (two_pi * slice) / params.slices; + const float sin_slice = sinf(slice_angle); + const float cos_slice = cosf(slice_angle); + const _sshape_vec4_t pos = _sshape_vec4(sin_slice * params.radius, y, cos_slice * params.radius, 1.0f); + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec4_t norm = _sshape_vec4(sin_slice, 0.0f, cos_slice, 0.0f); + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, norm)); + const _sshape_vec2_t uv = _sshape_vec2(slice * du, 1.0f - v); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + _sshape_build_cylinder_cap_ring(&buf, ¶ms, y1, -1.0f, du, 1.0f - dv, &rand_seed); + _sshape_build_cylinder_cap_pole(&buf, ¶ms, y1, -1.0f, du, 1.0f, &rand_seed); + + // generate indices + { + // top-cap indices + const uint16_t row_a = start_index; + const uint16_t row_b = row_a + params.slices + 1; + for (uint16_t slice = 0; slice < params.slices; slice++) { + _sshape_add_triangle(&buf, row_a + slice, row_b + slice + 1, row_b + slice); + } + } + // shaft triangles + for (uint16_t stack = 0; stack < params.stacks; stack++) { + const uint16_t row_a = start_index + (stack + 2) * (params.slices + 1); + const uint16_t row_b = row_a + params.slices + 1; + for (uint16_t slice = 0; slice < params.slices; slice++) { + _sshape_add_triangle(&buf, row_a + slice, row_a + slice + 1, row_b + slice + 1); + _sshape_add_triangle(&buf, row_a + slice, row_b + slice + 1, row_b + slice); + } + } + { + // bottom-cap indices + const uint16_t row_a = start_index + (params.stacks + 3) * (params.slices + 1); + const uint16_t row_b = row_a + params.slices + 1; + for (uint16_t slice = 0; slice < params.slices; slice++) { + _sshape_add_triangle(&buf, row_a + slice, row_a + slice + 1, row_b + slice + 1); + } + } + return buf; +} + +/* + Geometry layout for torus (sides = 4, rings = 5): + + +--+--+--+--+--+ + |\ |\ |\ |\ |\ | + | \| \| \| \| \| + +--+--+--+--+--+ 30 vertices (sides + 1) * (rings + 1) + |\ |\ |\ |\ |\ | 40 triangles (2 * sides * rings) + | \| \| \| \| \| + +--+--+--+--+--+ + |\ |\ |\ |\ |\ | + | \| \| \| \| \| + +--+--+--+--+--+ + |\ |\ |\ |\ |\ | + | \| \| \| \| \| + +--+--+--+--+--+ +*/ +SOKOL_API_IMPL sshape_buffer_t sshape_build_torus(const sshape_buffer_t* in_buf, const sshape_torus_t* in_params) { + SOKOL_ASSERT(in_buf && in_params); + const sshape_torus_t params = _sshape_torus_defaults(in_params); + const uint32_t num_vertices = _sshape_torus_num_vertices(params.sides, params.rings); + const uint32_t num_indices = _sshape_torus_num_indices(params.sides, params.rings); + sshape_buffer_t buf = *in_buf; + if (!_sshape_validate_buffer(&buf, num_vertices, num_indices)) { + buf.valid = false; + return buf; + } + buf.valid = true; + const uint16_t start_index = _sshape_base_index(&buf); + if (!params.merge) { + _sshape_advance_offset(&buf.vertices); + _sshape_advance_offset(&buf.indices); + } + + uint32_t rand_seed = 0x12345678; + const float two_pi = 2.0f * 3.14159265358979323846f; + const float dv = 1.0f / params.sides; + const float du = 1.0f / params.rings; + + // generate vertices + for (uint32_t side = 0; side <= params.sides; side++) { + const float phi = (side * two_pi) / params.sides; + const float sin_phi = sinf(phi); + const float cos_phi = cosf(phi); + for (uint32_t ring = 0; ring <= params.rings; ring++) { + const float theta = (ring * two_pi) / params.rings; + const float sin_theta = sinf(theta); + const float cos_theta = cosf(theta); + + // torus surface position + const float spx = sin_theta * (params.radius - (params.ring_radius * cos_phi)); + const float spy = sin_phi * params.ring_radius; + const float spz = cos_theta * (params.radius - (params.ring_radius * cos_phi)); + + // torus position with ring-radius zero (for normal computation) + const float ipx = sin_theta * params.radius; + const float ipy = 0.0f; + const float ipz = cos_theta * params.radius; + + const _sshape_vec4_t pos = _sshape_vec4(spx, spy, spz, 1.0f); + const _sshape_vec4_t norm = _sshape_vec4(spx - ipx, spy - ipy, spz - ipz, 0.0f); + const _sshape_vec4_t tpos = _sshape_mat4_mul(¶ms.transform, pos); + const _sshape_vec4_t tnorm = _sshape_vec4_norm(_sshape_mat4_mul(¶ms.transform, norm)); + const _sshape_vec2_t uv = _sshape_vec2(ring * du, 1.0f - side * dv); + const uint32_t color = params.random_colors ? _sshape_rand_color(&rand_seed) : params.color; + _sshape_add_vertex(&buf, tpos, tnorm, uv, color); + } + } + + // generate indices + for (uint16_t side = 0; side < params.sides; side++) { + const uint16_t row_a = start_index + side * (params.rings + 1); + const uint16_t row_b = row_a + params.rings + 1; + for (uint16_t ring = 0; ring < params.rings; ring++) { + _sshape_add_triangle(&buf, row_a + ring, row_a + ring + 1, row_b + ring + 1); + _sshape_add_triangle(&buf, row_a + ring, row_b + ring + 1, row_b + ring); + } + } + return buf; +} + +SOKOL_API_IMPL sg_buffer_desc sshape_vertex_buffer_desc(const sshape_buffer_t* buf) { + SOKOL_ASSERT(buf && buf->valid); + sg_buffer_desc desc = { 0 }; + if (buf->valid) { + desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + desc.usage = SG_USAGE_IMMUTABLE; + desc.data.ptr = buf->vertices.buffer.ptr; + desc.data.size = buf->vertices.data_size; + } + return desc; +} + +SOKOL_API_IMPL sg_buffer_desc sshape_index_buffer_desc(const sshape_buffer_t* buf) { + SOKOL_ASSERT(buf && buf->valid); + sg_buffer_desc desc = { 0 }; + if (buf->valid) { + desc.type = SG_BUFFERTYPE_INDEXBUFFER; + desc.usage = SG_USAGE_IMMUTABLE; + desc.data.ptr = buf->indices.buffer.ptr; + desc.data.size = buf->indices.data_size; + } + return desc; +} + +SOKOL_SHAPE_API_DECL sshape_element_range_t sshape_element_range(const sshape_buffer_t* buf) { + SOKOL_ASSERT(buf && buf->valid); + SOKOL_ASSERT(buf->indices.shape_offset < buf->indices.data_size); + SOKOL_ASSERT(0 == (buf->indices.shape_offset & (sizeof(uint16_t) - 1))); + SOKOL_ASSERT(0 == (buf->indices.data_size & (sizeof(uint16_t) - 1))); + sshape_element_range_t range = { 0 }; + range.base_element = (int) (buf->indices.shape_offset / sizeof(uint16_t)); + if (buf->valid) { + range.num_elements = (int) ((buf->indices.data_size - buf->indices.shape_offset) / sizeof(uint16_t)); + } + else { + range.num_elements = 0; + } + return range; +} + +SOKOL_API_IMPL sg_vertex_buffer_layout_state sshape_vertex_buffer_layout_state(void) { + sg_vertex_buffer_layout_state state = { 0 }; + state.stride = sizeof(sshape_vertex_t); + return state; +} + +SOKOL_API_IMPL sg_vertex_attr_state sshape_position_vertex_attr_state(void) { + sg_vertex_attr_state state = { 0 }; + state.offset = offsetof(sshape_vertex_t, x); + state.format = SG_VERTEXFORMAT_FLOAT3; + return state; +} + +SOKOL_API_IMPL sg_vertex_attr_state sshape_normal_vertex_attr_state(void) { + sg_vertex_attr_state state = { 0 }; + state.offset = offsetof(sshape_vertex_t, normal); + state.format = SG_VERTEXFORMAT_BYTE4N; + return state; +} + +SOKOL_API_IMPL sg_vertex_attr_state sshape_texcoord_vertex_attr_state(void) { + sg_vertex_attr_state state = { 0 }; + state.offset = offsetof(sshape_vertex_t, u); + state.format = SG_VERTEXFORMAT_USHORT2N; + return state; +} + +SOKOL_API_IMPL sg_vertex_attr_state sshape_color_vertex_attr_state(void) { + sg_vertex_attr_state state = { 0 }; + state.offset = offsetof(sshape_vertex_t, color); + state.format = SG_VERTEXFORMAT_UBYTE4N; + return state; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#endif // SOKOL_SHAPE_IMPL diff --git a/modules/sokol-jai/sokol/c/sokol_time.c b/modules/sokol-jai/sokol/c/sokol_time.c new file mode 100644 index 0000000..335304c --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_time.c @@ -0,0 +1,5 @@ +#if defined(IMPL) +#define SOKOL_TIME_IMPL +#endif +#include "sokol_defines.h" +#include "sokol_time.h" diff --git a/modules/sokol-jai/sokol/c/sokol_time.h b/modules/sokol-jai/sokol/c/sokol_time.h new file mode 100644 index 0000000..fd766d8 --- /dev/null +++ b/modules/sokol-jai/sokol/c/sokol_time.h @@ -0,0 +1,319 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_IMPL +#endif +#ifndef SOKOL_TIME_INCLUDED +/* + sokol_time.h -- simple cross-platform time measurement + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_TIME_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_TIME_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_time.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + void stm_setup(); + Call once before any other functions to initialize sokol_time + (this calls for instance QueryPerformanceFrequency on Windows) + + uint64_t stm_now(); + Get current point in time in unspecified 'ticks'. The value that + is returned has no relation to the 'wall-clock' time and is + not in a specific time unit, it is only useful to compute + time differences. + + uint64_t stm_diff(uint64_t new, uint64_t old); + Computes the time difference between new and old. This will always + return a positive, non-zero value. + + uint64_t stm_since(uint64_t start); + Takes the current time, and returns the elapsed time since start + (this is a shortcut for "stm_diff(stm_now(), start)") + + uint64_t stm_laptime(uint64_t* last_time); + This is useful for measuring frame time and other recurring + events. It takes the current time, returns the time difference + to the value in last_time, and stores the current time in + last_time for the next call. If the value in last_time is 0, + the return value will be zero (this usually happens on the + very first call). + + uint64_t stm_round_to_common_refresh_rate(uint64_t duration) + This oddly named function takes a measured frame time and + returns the closest "nearby" common display refresh rate frame duration + in ticks. If the input duration isn't close to any common display + refresh rate, the input duration will be returned unchanged as a fallback. + The main purpose of this function is to remove jitter/inaccuracies from + measured frame times, and instead use the display refresh rate as + frame duration. + NOTE: for more robust frame timing, consider using the + sokol_app.h function sapp_frame_duration() + + Use the following functions to convert a duration in ticks into + useful time units: + + double stm_sec(uint64_t ticks); + double stm_ms(uint64_t ticks); + double stm_us(uint64_t ticks); + double stm_ns(uint64_t ticks); + Converts a tick value into seconds, milliseconds, microseconds + or nanoseconds. Note that not all platforms will have nanosecond + or even microsecond precision. + + Uses the following time measurement functions under the hood: + + Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() + MacOS/iOS: mach_absolute_time() + emscripten: emscripten_get_now() + Linux+others: clock_gettime(CLOCK_MONOTONIC) + + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_TIME_INCLUDED (1) +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL) +#define SOKOL_TIME_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_TIME_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_TIME_API_DECL __declspec(dllimport) +#else +#define SOKOL_TIME_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_TIME_API_DECL void stm_setup(void); +SOKOL_TIME_API_DECL uint64_t stm_now(void); +SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); +SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks); +SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time); +SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); +SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_us(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif // SOKOL_TIME_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_TIME_IMPL +#define SOKOL_TIME_IMPL_INCLUDED (1) +#include /* memset */ + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +typedef struct { + uint32_t initialized; + LARGE_INTEGER freq; + LARGE_INTEGER start; +} _stm_state_t; +#elif defined(__APPLE__) && defined(__MACH__) +#include +typedef struct { + uint32_t initialized; + mach_timebase_info_data_t timebase; + uint64_t start; +} _stm_state_t; +#elif defined(__EMSCRIPTEN__) +#include +typedef struct { + uint32_t initialized; + double start; +} _stm_state_t; +#else /* anything else, this will need more care for non-Linux platforms */ +#ifdef ESP8266 +// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined +#define CLOCK_MONOTONIC 0 +#endif +#include +typedef struct { + uint32_t initialized; + uint64_t start; +} _stm_state_t; +#endif +static _stm_state_t _stm; + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +_SOKOL_PRIVATE int64_t _stm_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif + +SOKOL_API_IMPL void stm_setup(void) { + memset(&_stm, 0, sizeof(_stm)); + _stm.initialized = 0xABCDABCD; + #if defined(_WIN32) + QueryPerformanceFrequency(&_stm.freq); + QueryPerformanceCounter(&_stm.start); + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_stm.timebase); + _stm.start = mach_absolute_time(); + #elif defined(__EMSCRIPTEN__) + _stm.start = emscripten_get_now(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +SOKOL_API_IMPL uint64_t stm_now(void) { + SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); + uint64_t now; + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t) _stm_int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); + #elif defined(__APPLE__) && defined(__MACH__) + const uint64_t mach_now = mach_absolute_time() - _stm.start; + now = (uint64_t) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); + #elif defined(__EMSCRIPTEN__) + double js_now = emscripten_get_now() - _stm.start; + now = (uint64_t) (js_now * 1000000.0); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; + #endif + return now; +} + +SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { + if (new_ticks > old_ticks) { + return new_ticks - old_ticks; + } + else { + return 1; + } +} + +SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { + return stm_diff(stm_now(), start_ticks); +} + +SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { + SOKOL_ASSERT(last_time); + uint64_t dt = 0; + uint64_t now = stm_now(); + if (0 != *last_time) { + dt = stm_diff(now, *last_time); + } + *last_time = now; + return dt; +} + +// first number is frame duration in ns, second number is tolerance in ns, +// the resulting min/max values must not overlap! +static const uint64_t _stm_refresh_rates[][2] = { + { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms + { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms + { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms + { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 + { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms + { 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms + { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms + { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms + { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms + { 0, 0 }, // keep the last element always at zero +}; + +SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { + uint64_t ns; + int i = 0; + while (0 != (ns = _stm_refresh_rates[i][0])) { + uint64_t tol = _stm_refresh_rates[i][1]; + if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { + return ns; + } + i++; + } + // fallthrough: didn't fit into any buckets + return ticks; +} + +SOKOL_API_IMPL double stm_sec(uint64_t ticks) { + return (double)ticks / 1000000000.0; +} + +SOKOL_API_IMPL double stm_ms(uint64_t ticks) { + return (double)ticks / 1000000.0; +} + +SOKOL_API_IMPL double stm_us(uint64_t ticks) { + return (double)ticks / 1000.0; +} + +SOKOL_API_IMPL double stm_ns(uint64_t ticks) { + return (double)ticks; +} +#endif /* SOKOL_TIME_IMPL */ + diff --git a/modules/sokol-jai/sokol/c/stb_truetype.h b/modules/sokol-jai/sokol/c/stb_truetype.h new file mode 100644 index 0000000..98a12c9 --- /dev/null +++ b/modules/sokol-jai/sokol/c/stb_truetype.h @@ -0,0 +1,4548 @@ +// stb_truetype.h - v1.16 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// Thomas Fields +// Derek Vinyard +// Cort Stratton +// +// VERSION HISTORY +// +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +// NOTE sokol-samples: declared static but never defined: T_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + assert(b->data); + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + // SOKOL FIXME: static analyzer fix 'Value stored to 'dx' is never read' + //dx = -dx; + dy = -dy; + // SOKOL FIXME: static analyzer fix 'Although the value stored to 'xb' is used in the enclosing expression, the value is never actually read from 'xb'' + /*t = x0,*/x0 = xb/*xb = t*/; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/modules/sokol-jai/sokol/debugtext/module.jai b/modules/sokol-jai/sokol/debugtext/module.jai new file mode 100644 index 0000000..748a61a --- /dev/null +++ b/modules/sokol-jai/sokol/debugtext/module.jai @@ -0,0 +1,685 @@ +// machine generated, do not edit + +/* + + sokol_debugtext.h - simple ASCII debug text rendering on top of sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_DEBUGTEXT_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + ...optionally provide the following macros to override defaults: + + SOKOL_VSNPRINTF - the function name of an alternative vsnprintf() function (default: vsnprintf) + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_DEBUGTEXT_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_DEBUGTEXT_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + If sokol_debugtext.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_DEBUGTEXT_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before including sokol_debugtext.h: + + sokol_gfx.h + + FEATURES AND CONCEPTS + ===================== + - renders 8-bit ASCII text as fixed-size 8x8 pixel characters + - comes with 6 embedded 8-bit home computer fonts (each taking up 2 KBytes) + - easily plug in your own fonts + - create multiple contexts for rendering text in different layers or render passes + + STEP BY STEP + ============ + + --- to initialize sokol-debugtext, call sdtx_setup() *after* initializing + sokol-gfx: + + sdtx_setup(&(sdtx_desc_t){ ... }); + + To see any warnings and errors, you should always install a logging callback. + The easiest way is via sokol_log.h: + + #include "sokol_log.h" + + sdtx_setup(&(sdtx_desc_t){ + .logger.func = slog_func, + }); + + --- configure sokol-debugtext by populating the sdtx_desc_t struct: + + .context_pool_size (default: 8) + The max number of text contexts that can be created. + + .printf_buf_size (default: 4096) + The size of the internal text formatting buffer used by + sdtx_printf() and sdtx_vprintf(). + + .fonts (default: none) + An array of sdtx_font_desc_t structs used to configure the + fonts that can be used for rendering. To use all builtin + fonts call sdtx_setup() like this (in C99): + + sdtx_setup(&(sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = sdtx_font_kc854(), + [2] = sdtx_font_z1013(), + [3] = sdtx_font_cpc(), + [4] = sdtx_font_c64(), + [5] = sdtx_font_oric() + } + }); + + For documentation on how to use you own font data, search + below for "USING YOUR OWN FONT DATA". + + .context + The setup parameters for the default text context. This will + be active right after sdtx_setup(), or when calling + sdtx_set_context(SDTX_DEFAULT_CONTEXT): + + .max_commands (default: 4096) + The max number of render commands that can be recorded + into the internal command buffer. This directly translates + to the number of render layer changes in a single frame. + + .char_buf_size (default: 4096) + The number of characters that can be rendered per frame in this + context, defines the size of an internal fixed-size vertex + buffer. Any additional characters will be silently ignored. + + .canvas_width (default: 640) + .canvas_height (default: 480) + The 'virtual canvas size' in pixels. This defines how big + characters will be rendered relative to the default framebuffer + dimensions. Each character occupies a grid of 8x8 'virtual canvas + pixels' (so a virtual canvas size of 640x480 means that 80x60 characters + fit on the screen). For rendering in a resizeable window, you + should dynamically update the canvas size in each frame by + calling sdtx_canvas(w, h). + + .tab_width (default: 4) + The width of a tab character in number of character cells. + + .color_format (default: 0) + .depth_format (default: 0) + .sample_count (default: 0) + The pixel format description for the default context needed + for creating the context's sg_pipeline object. When + rendering to the default framebuffer you can leave those + zero-initialized, in this case the proper values will be + filled in by sokol-gfx. You only need to provide non-default + values here when rendering to render targets with different + pixel format attributes than the default framebuffer. + + --- Before starting to render text, optionally call sdtx_canvas() to + dynamically resize the virtual canvas. This is recommended when + rendering to a resizeable window. The virtual canvas size can + also be used to scale text in relation to the display resolution. + + Examples when using sokol-app: + + - to render characters at 8x8 'physical pixels': + + sdtx_canvas(sapp_width(), sapp_height()); + + - to render characters at 16x16 physical pixels: + + sdtx_canvas(sapp_width()/2.0f, sapp_height()/2.0f); + + Do *not* use integer math here, since this will not look nice + when the render target size isn't divisible by 2. + + --- Optionally define the origin for the character grid with: + + sdtx_origin(x, y); + + The provided coordinates are in character grid cells, not in + virtual canvas pixels. E.g. to set the origin to 2 character tiles + from the left and top border: + + sdtx_origin(2, 2); + + You can define fractions, e.g. to start rendering half + a character tile from the top-left corner: + + sdtx_origin(0.5f, 0.5f); + + --- Optionally set a different font by calling: + + sdtx_font(font_index) + + sokol-debugtext provides 8 font slots which can be populated + with the builtin fonts or with user-provided font data, so + 'font_index' must be a number from 0 to 7. + + --- Position the text cursor with one of the following calls. All arguments + are in character grid cells as floats and relative to the + origin defined with sdtx_origin(): + + sdtx_pos(x, y) - sets absolute cursor position + sdtx_pos_x(x) - only set absolute x cursor position + sdtx_pos_y(y) - only set absolute y cursor position + + sdtx_move(x, y) - move cursor relative in x and y direction + sdtx_move_x(x) - move cursor relative only in x direction + sdtx_move_y(y) - move cursor relative only in y direction + + sdtx_crlf() - set cursor to beginning of next line + (same as sdtx_pos_x(0) + sdtx_move_y(1)) + sdtx_home() - resets the cursor to the origin + (same as sdtx_pos(0, 0)) + + --- Set a new text color with any of the following functions: + + sdtx_color3b(r, g, b) - RGB 0..255, A=255 + sdtx_color3f(r, g, b) - RGB 0.0f..1.0f, A=1.0f + sdtx_color4b(r, g, b, a) - RGBA 0..255 + sdtx_color4f(r, g, b, a) - RGBA 0.0f..1.0f + sdtx_color1i(uint32_t rgba) - ABGR (0xAABBGGRR) + + --- Output 8-bit ASCII text with the following functions: + + sdtx_putc(c) - output a single character + + sdtx_puts(str) - output a null-terminated C string, note that + this will *not* append a newline (so it behaves + differently than the CRT's puts() function) + + sdtx_putr(str, len) - 'put range' output the first 'len' characters of + a C string or until the zero character is encountered + + sdtx_printf(fmt, ...) - output with printf-formatting, note that you + can inject your own printf-compatible function + by overriding the SOKOL_VSNPRINTF define before + including the implementation + + sdtx_vprintf(fmt, args) - same as sdtx_printf() but with the arguments + provided in a va_list + + - Note that the text will not yet be rendered, only recorded for rendering + at a later time, the actual rendering happens when sdtx_draw() is called + inside a sokol-gfx render pass. + - This means also you can output text anywhere in the frame, it doesn't + have to be inside a render pass. + - Note that character codes <32 are reserved as control characters + and won't render anything. Currently only the following control + characters are implemented: + + \r - carriage return (same as sdtx_pos_x(0)) + \n - carriage return + line feed (same as stdx_crlf()) + \t - a tab character + + --- You can 'record' text into render layers, this allows to mix/interleave + sokol-debugtext rendering with other rendering operations inside + sokol-gfx render passes. To start recording text into a different render + layer, call: + + sdtx_layer(int layer_id) + + ...outside a sokol-gfx render pass. + + --- finally, from within a sokol-gfx render pass, call: + + sdtx_draw() + + ...for non-layered rendering, or to draw a specific layer: + + sdtx_draw_layer(int layer_id) + + NOTE that sdtx_draw() is equivalent to: + + sdtx_draw_layer(0) + + ...so sdtx_draw() will *NOT* render all text layers, instead it will + only render the 'default layer' 0. + + --- at the end of a frame (defined by the call to sg_commit()), sokol-debugtext + will rewind all contexts: + + - the internal vertex index is set to 0 + - the internal command index is set to 0 + - the current layer id is set to 0 + - the current font is set to 0 + - the cursor position is reset + + + RENDERING WITH MULTIPLE CONTEXTS + ================================ + Use multiple text contexts if you need to render debug text in different + sokol-gfx render passes, or want to render text to different layers + in the same render pass, each with its own set of parameters. + + To create a new text context call: + + sdtx_context ctx = sdtx_make_context(&(sdtx_context_desc_t){ ... }); + + The creation parameters in the sdtx_context_desc_t struct are the same + as already described above in the sdtx_setup() function: + + .char_buf_size -- max number of characters rendered in one frame, default: 4096 + .canvas_width -- the initial virtual canvas width, default: 640 + .canvas_height -- the initial virtual canvas height, default: 400 + .tab_width -- tab width in number of characters, default: 4 + .color_format -- color pixel format of target render pass + .depth_format -- depth pixel format of target render pass + .sample_count -- MSAA sample count of target render pass + + To make a new context the active context, call: + + sdtx_set_context(ctx) + + ...and after that call the text output functions as described above, and + finally, inside a sokol-gfx render pass, call sdtx_draw() to actually + render the text for this context. + + A context keeps track of the following parameters: + + - the active font + - the virtual canvas size + - the origin position + - the current cursor position + - the current tab width + - the current color + - and the current layer-id + + You can get the currently active context with: + + sdtx_get_context() + + To make the default context current, call sdtx_set_context() with the + special SDTX_DEFAULT_CONTEXT handle: + + sdtx_set_context(SDTX_DEFAULT_CONTEXT) + + Alternatively, use the function sdtx_default_context() to get the default + context handle: + + sdtx_set_context(sdtx_default_context()); + + To destroy a context, call: + + sdtx_destroy_context(ctx) + + If a context is set as active that no longer exists, all sokol-debugtext + functions that require an active context will silently fail. + + You can directly draw the recorded text in a specific context without + setting the active context: + + sdtx_context_draw(ctx) + sdtx_context_draw_layer(ctx, layer_id) + + USING YOUR OWN FONT DATA + ======================== + + Instead of the built-in fonts you can also plug your own font data + into sokol-debugtext by providing one or several sdtx_font_desc_t + structures in the sdtx_setup call. + + For instance to use a built-in font at slot 0, and a user-font at + font slot 1, the sdtx_setup() call might look like this: + + sdtx_setup(&sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = { + .data = { + .ptr = my_font_data, + .size = sizeof(my_font_data) + }, + .first_char = ..., + .last_char = ... + } + } + }); + + Where 'my_font_data' is a byte array where every character is described + by 8 bytes arranged like this: + + bits + 7 6 5 4 3 2 1 0 + . . . X X . . . byte 0: 0x18 + . . X X X X . . byte 1: 0x3C + . X X . . X X . byte 2: 0x66 + . X X . . X X . byte 3: 0x66 + . X X X X X X . byte 4: 0x7E + . X X . . X X . byte 5: 0x66 + . X X . . X X . byte 6: 0x66 + . . . . . . . . byte 7: 0x00 + + A complete font consists of 256 characters, resulting in 2048 bytes for + the font data array (but note that the character codes 0..31 will never + be rendered). + + If you provide such a complete font data array, you can drop the .first_char + and .last_char initialization parameters since those default to 0 and 255, + note that you can also use the SDTX_RANGE() helper macro to build the + .data item: + + sdtx_setup(&sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = { + .data = SDTX_RANGE(my_font_data) + } + } + }); + + If the font doesn't define all 256 character tiles, or you don't need an + entire 256-character font and want to save a couple of bytes, use the + .first_char and .last_char initialization parameters to define a sub-range. + For instance if the font only contains the characters between the Space + (ASCII code 32) and uppercase character 'Z' (ASCII code 90): + + sdtx_setup(&sdtx_desc_t){ + .fonts = { + [0] = sdtx_font_kc853(), + [1] = { + .data = SDTX_RANGE(my_font_data), + .first_char = 32, // could also write ' ' + .last_char = 90 // could also write 'Z' + } + } + }); + + Character tiles that haven't been defined in the font will be rendered + as a solid 8x8 quad. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sdtx_setup(&(sdtx_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sdtx_setup(&(sdtx_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sdtx' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SDTX_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_debugtext.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-debugtext like this: + + sdtx_setup(&(sdtx_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2020 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ +#import,dir "../gfx"(DEBUG = USE_DLL, USE_GL = USE_DLL, USE_DLL = USE_DLL); + +sdtx_printf :: (s: string, args: ..Any) { + #import "Basic"; + fstr := tprint(s, ..args); + sdtx_putr(to_c_string(fstr), xx fstr.count); +} +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_debugtext_clib :: #library "sokol_debugtext_windows_x64_gl_debug"; } + else { sokol_debugtext_clib :: #library "sokol_debugtext_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_debugtext_clib :: #library "sokol_debugtext_windows_x64_d3d11_debug"; } + else { sokol_debugtext_clib :: #library "sokol_debugtext_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_windows_x64_gl_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_windows_x64_d3d11_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_debugtext_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_arm64_gl_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_x64_gl_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_arm64_metal_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_x64_metal_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_linux_x64_gl_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_wasm_gl_debug"; } + else { sokol_debugtext_clib :: #library,no_dll "sokol_debugtext_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// initialization/shutdown +sdtx_setup :: (desc: *sdtx_desc_t) -> void #foreign sokol_debugtext_clib; +sdtx_shutdown :: () -> void #foreign sokol_debugtext_clib; +// builtin font data (use to populate sdtx_desc.font[]) +sdtx_font_kc853 :: () -> sdtx_font_desc_t #foreign sokol_debugtext_clib; +sdtx_font_kc854 :: () -> sdtx_font_desc_t #foreign sokol_debugtext_clib; +sdtx_font_z1013 :: () -> sdtx_font_desc_t #foreign sokol_debugtext_clib; +sdtx_font_cpc :: () -> sdtx_font_desc_t #foreign sokol_debugtext_clib; +sdtx_font_c64 :: () -> sdtx_font_desc_t #foreign sokol_debugtext_clib; +sdtx_font_oric :: () -> sdtx_font_desc_t #foreign sokol_debugtext_clib; +// context functions +sdtx_make_context :: (desc: *sdtx_context_desc_t) -> sdtx_context #foreign sokol_debugtext_clib; +sdtx_destroy_context :: (ctx: sdtx_context) -> void #foreign sokol_debugtext_clib; +sdtx_set_context :: (ctx: sdtx_context) -> void #foreign sokol_debugtext_clib; +sdtx_get_context :: () -> sdtx_context #foreign sokol_debugtext_clib; +sdtx_default_context :: () -> sdtx_context #foreign sokol_debugtext_clib; +// drawing functions (call inside sokol-gfx render pass) +sdtx_draw :: () -> void #foreign sokol_debugtext_clib; +sdtx_context_draw :: (ctx: sdtx_context) -> void #foreign sokol_debugtext_clib; +sdtx_draw_layer :: (layer_id: s32) -> void #foreign sokol_debugtext_clib; +sdtx_context_draw_layer :: (ctx: sdtx_context, layer_id: s32) -> void #foreign sokol_debugtext_clib; +// switch render layer +sdtx_layer :: (layer_id: s32) -> void #foreign sokol_debugtext_clib; +// switch to a different font +sdtx_font :: (font_index: s32) -> void #foreign sokol_debugtext_clib; +// set a new virtual canvas size in screen pixels +sdtx_canvas :: (w: float, h: float) -> void #foreign sokol_debugtext_clib; +// set a new origin in character grid coordinates +sdtx_origin :: (x: float, y: float) -> void #foreign sokol_debugtext_clib; +// cursor movement functions (relative to origin in character grid coordinates) +sdtx_home :: () -> void #foreign sokol_debugtext_clib; +sdtx_pos :: (x: float, y: float) -> void #foreign sokol_debugtext_clib; +sdtx_pos_x :: (x: float) -> void #foreign sokol_debugtext_clib; +sdtx_pos_y :: (y: float) -> void #foreign sokol_debugtext_clib; +sdtx_move :: (dx: float, dy: float) -> void #foreign sokol_debugtext_clib; +sdtx_move_x :: (dx: float) -> void #foreign sokol_debugtext_clib; +sdtx_move_y :: (dy: float) -> void #foreign sokol_debugtext_clib; +sdtx_crlf :: () -> void #foreign sokol_debugtext_clib; +// set the current text color +sdtx_color3b :: (r: u8, g: u8, b: u8) -> void #foreign sokol_debugtext_clib; +sdtx_color3f :: (r: float, g: float, b: float) -> void #foreign sokol_debugtext_clib; +sdtx_color4b :: (r: u8, g: u8, b: u8, a: u8) -> void #foreign sokol_debugtext_clib; +sdtx_color4f :: (r: float, g: float, b: float, a: float) -> void #foreign sokol_debugtext_clib; +sdtx_color1i :: (rgba: u32) -> void #foreign sokol_debugtext_clib; +// text rendering +sdtx_putc :: (c: u8) -> void #foreign sokol_debugtext_clib; +sdtx_puts :: (str: *u8) -> void #foreign sokol_debugtext_clib; +sdtx_putr :: (str: *u8, len: s32) -> void #foreign sokol_debugtext_clib; + +sdtx_log_item_t :: enum u32 { + OK; + MALLOC_FAILED; + ADD_COMMIT_LISTENER_FAILED; + COMMAND_BUFFER_FULL; + CONTEXT_POOL_EXHAUSTED; + CANNOT_DESTROY_DEFAULT_CONTEXT; +} + +sdtx_logger_t :: struct { + func : (a0: *u8, a1: u32, a2: u32, a3: *u8, a4: u32, a5: *u8, a6: *void) #c_call; + user_data : *void; +} + +sdtx_context :: struct { + id : u32; +} + +sdtx_range :: struct { + ptr : *void; + size : u64; +} + +sdtx_font_desc_t :: struct { + data : sdtx_range; + first_char : u8; + last_char : u8; +} + +sdtx_context_desc_t :: struct { + max_commands : s32; + char_buf_size : s32; + canvas_width : float; + canvas_height : float; + tab_width : s32; + color_format : sg_pixel_format; + depth_format : sg_pixel_format; + sample_count : s32; +} + +sdtx_allocator_t :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +sdtx_desc_t :: struct { + context_pool_size : s32; + printf_buf_size : s32; + fonts : [8]sdtx_font_desc_t; + ctx : sdtx_context_desc_t; + allocator : sdtx_allocator_t; + logger : sdtx_logger_t; +} + diff --git a/modules/sokol-jai/sokol/fetch/module.jai b/modules/sokol-jai/sokol/fetch/module.jai new file mode 100644 index 0000000..4f49bd9 --- /dev/null +++ b/modules/sokol-jai/sokol/fetch/module.jai @@ -0,0 +1,1105 @@ +// machine generated, do not edit + +/* + + sokol_fetch.h -- asynchronous data loading/streaming + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_FETCH_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_FETCH_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_FETCH_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SFETCH_MAX_PATH - max length of UTF-8 filesystem path / URL (default: 1024 bytes) + SFETCH_MAX_USERDATA_UINT64 - max size of embedded userdata in number of uint64_t, userdata + will be copied into an 8-byte aligned memory region associated + with each in-flight request, default value is 16 (== 128 bytes) + SFETCH_MAX_CHANNELS - max number of IO channels (default is 16, also see sfetch_desc_t.num_channels) + + If sokol_fetch.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_FETCH_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + NOTE: The following documentation talks a lot about "IO threads". Actual + threads are only used on platforms where threads are available. The web + version (emscripten/wasm) doesn't use POSIX-style threads, but instead + asynchronous Javascript calls chained together by callbacks. The actual + source code differences between the two approaches have been kept to + a minimum though. + + FEATURE OVERVIEW + ================ + + - Asynchronously load complete files, or stream files incrementally via + HTTP (on web platform), or the local file system (on native platforms) + + - Request / response-callback model, user code sends a request + to initiate a file-load, sokol_fetch.h calls the response callback + on the same thread when data is ready or user-code needs + to respond otherwise + + - Not limited to the main-thread or a single thread: A sokol-fetch + "context" can live on any thread, and multiple contexts + can operate side-by-side on different threads. + + - Memory management for data buffers is under full control of user code. + sokol_fetch.h won't allocate memory after it has been setup. + + - Automatic rate-limiting guarantees that only a maximum number of + requests is processed at any one time, allowing a zero-allocation + model, where all data is streamed into fixed-size, pre-allocated + buffers. + + - Active Requests can be paused, continued and cancelled from anywhere + in the user-thread which sent this request. + + + TL;DR EXAMPLE CODE + ================== + This is the most-simple example code to load a single data file with a + known maximum size: + + (1) initialize sokol-fetch with default parameters (but NOTE that the + default setup parameters provide a safe-but-slow "serialized" + operation). In order to see any logging output in case or errors + you should always provide a logging function + (such as 'slog_func' from sokol_log.h): + + sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func }); + + (2) send a fetch-request to load a file from the current directory + into a buffer big enough to hold the entire file content: + + static uint8_t buf[MAX_FILE_SIZE]; + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = response_callback, + .buffer = { + .ptr = buf, + .size = sizeof(buf) + } + }); + + If 'buf' is a value (e.g. an array or struct item), the .buffer item can + be initialized with the SFETCH_RANGE() helper macro: + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = response_callback, + .buffer = SFETCH_RANGE(buf) + }); + + (3) write a 'response-callback' function, this will be called whenever + the user-code must respond to state changes of the request + (most importantly when data has been loaded): + + void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // data has been loaded, and is available via the + // sfetch_range_t struct item 'data': + const void* ptr = response->data.ptr; + size_t num_bytes = response->data.size; + } + if (response->finished) { + // the 'finished'-flag is the catch-all flag for when the request + // is finished, no matter if loading was successful or failed, + // so any cleanup-work should happen here... + ... + if (response->failed) { + // 'failed' is true in (addition to 'finished') if something + // went wrong (file doesn't exist, or less bytes could be + // read from the file than expected) + } + } + } + + (4) pump the sokol-fetch message queues, and invoke response callbacks + by calling: + + sfetch_dowork(); + + In an event-driven app this should be called in the event loop. If you + use sokol-app this would be in your frame_cb function. + + (5) finally, call sfetch_shutdown() at the end of the application: + + There's many other loading-scenarios, for instance one doesn't have to + provide a buffer upfront, this can also happen in the response callback. + + Or it's possible to stream huge files into small fixed-size buffer, + complete with pausing and continuing the download. + + It's also possible to improve the 'pipeline throughput' by fetching + multiple files in parallel, but at the same time limit the maximum + number of requests that can be 'in-flight'. + + For how this all works, please read the following documentation sections :) + + + API DOCUMENTATION + ================= + + void sfetch_setup(const sfetch_desc_t* desc) + -------------------------------------------- + First call sfetch_setup(const sfetch_desc_t*) on any thread before calling + any other sokol-fetch functions on the same thread. + + sfetch_setup() takes a pointer to an sfetch_desc_t struct with setup + parameters. Parameters which should use their default values must + be zero-initialized: + + - max_requests (uint32_t): + The maximum number of requests that can be alive at any time, the + default is 128. + + - num_channels (uint32_t): + The number of "IO channels" used to parallelize and prioritize + requests, the default is 1. + + - num_lanes (uint32_t): + The number of "lanes" on a single channel. Each request which is + currently 'inflight' on a channel occupies one lane until the + request is finished. This is used for automatic rate-limiting + (search below for CHANNELS AND LANES for more details). The + default number of lanes is 1. + + For example, to setup sokol-fetch for max 1024 active requests, 4 channels, + and 8 lanes per channel in C99: + + sfetch_setup(&(sfetch_desc_t){ + .max_requests = 1024, + .num_channels = 4, + .num_lanes = 8 + }); + + sfetch_setup() is the only place where sokol-fetch will allocate memory. + + NOTE that the default setup parameters of 1 channel and 1 lane per channel + has a very poor 'pipeline throughput' since this essentially serializes + IO requests (a new request will only be processed when the last one has + finished), and since each request needs at least one roundtrip between + the user- and IO-thread the throughput will be at most one request per + frame. Search for LATENCY AND THROUGHPUT below for more information on + how to increase throughput. + + NOTE that you can call sfetch_setup() on multiple threads, each thread + will get its own thread-local sokol-fetch instance, which will work + independently from sokol-fetch instances on other threads. + + void sfetch_shutdown(void) + -------------------------- + Call sfetch_shutdown() at the end of the application to stop any + IO threads and free all memory that was allocated in sfetch_setup(). + + sfetch_handle_t sfetch_send(const sfetch_request_t* request) + ------------------------------------------------------------ + Call sfetch_send() to start loading data, the function takes a pointer to an + sfetch_request_t struct with request parameters and returns a + sfetch_handle_t identifying the request for later calls. At least + a path/URL and callback must be provided: + + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = my_response_callback + }); + + sfetch_send() will return an invalid handle if no request can be allocated + from the internal pool because all available request items are 'in-flight'. + + The sfetch_request_t struct contains the following parameters (optional + parameters that are not provided must be zero-initialized): + + - path (const char*, required) + Pointer to an UTF-8 encoded C string describing the filesystem + path or HTTP URL. The string will be copied into an internal data + structure, and passed "as is" (apart from any required + encoding-conversions) to fopen(), CreateFileW() or + the html fetch API call. The maximum length of the string is defined by + the SFETCH_MAX_PATH configuration define, the default is 1024 bytes + including the 0-terminator byte. + + - callback (sfetch_callback_t, required) + Pointer to a response-callback function which is called when the + request needs "user code attention". Search below for REQUEST + STATES AND THE RESPONSE CALLBACK for detailed information about + handling responses in the response callback. + + - channel (uint32_t, optional) + Index of the IO channel where the request should be processed. + Channels are used to parallelize and prioritize requests relative + to each other. Search below for CHANNELS AND LANES for more + information. The default channel is 0. + + - chunk_size (uint32_t, optional) + The chunk_size member is used for streaming data incrementally + in small chunks. After 'chunk_size' bytes have been loaded into + to the streaming buffer, the response callback will be called + with the buffer containing the fetched data for the current chunk. + If chunk_size is 0 (the default), than the whole file will be loaded. + Please search below for CHUNK SIZE AND HTTP COMPRESSION for + important information how streaming works if the web server + is serving compressed data. + + - buffer (sfetch_range_t) + This is a optional pointer/size pair describing a chunk of memory where + data will be loaded into (if no buffer is provided upfront, this + must happen in the response callback). If a buffer is provided, + it must be big enough to either hold the entire file (if chunk_size + is zero), or the *uncompressed* data for one downloaded chunk + (if chunk_size is > 0). + + - user_data (sfetch_range_t) + The user_data ptr/size range struct describe an optional POD blob + (plain-old-data) associated with the request which will be copied(!) + into an internal memory block. The maximum default size of this + memory block is 128 bytes (but can be overridden by defining + SFETCH_MAX_USERDATA_UINT64 before including the notification, note + that this define is in "number of uint64_t", not number of bytes). + The user-data block is 8-byte aligned, and will be copied via + memcpy() (so don't put any C++ "smart members" in there). + + NOTE that request handles are strictly thread-local and only unique + within the thread the handle was created on, and all function calls + involving a request handle must happen on that same thread. + + bool sfetch_handle_valid(sfetch_handle_t request) + ------------------------------------------------- + This checks if the provided request handle is valid, and is associated with + a currently active request. It will return false if: + + - sfetch_send() returned an invalid handle because it couldn't allocate + a new request from the internal request pool (because they're all + in flight) + - the request associated with the handle is no longer alive (because + it either finished successfully, or the request failed for some + reason) + + void sfetch_dowork(void) + ------------------------ + Call sfetch_dowork(void) in regular intervals (for instance once per frame) + on the same thread as sfetch_setup() to "turn the gears". If you are sending + requests but never hear back from them in the response callback function, then + the most likely reason is that you forgot to add the call to sfetch_dowork() + in the per-frame function. + + sfetch_dowork() roughly performs the following work: + + - any new requests that have been sent with sfetch_send() since the + last call to sfetch_dowork() will be dispatched to their IO channels + and assigned a free lane. If all lanes on that channel are occupied + by requests 'in flight', incoming requests must wait until + a lane becomes available + + - for all new requests which have been enqueued on a channel which + don't already have a buffer assigned the response callback will be + called with (response->dispatched == true) so that the response + callback can inspect the dynamically assigned lane and bind a buffer + to the request (search below for CHANNELS AND LANE for more info) + + - a state transition from "user side" to "IO thread side" happens for + each new request that has been dispatched to a channel. + + - requests dispatched to a channel are either forwarded into that + channel's worker thread (on native platforms), or cause an HTTP + request to be sent via an asynchronous fetch() call (on the web + platform) + + - for all requests which have finished their current IO operation a + state transition from "IO thread side" to "user side" happens, + and the response callback is called so that the fetched data + can be processed. + + - requests which are completely finished (either because the entire + file content has been loaded, or they are in the FAILED state) are + freed (this just changes their state in the 'request pool', no actual + memory is freed) + + - requests which are not yet finished are fed back into the + 'incoming' queue of their channel, and the cycle starts again, this + only happens for requests which perform data streaming (not load + the entire file at once). + + void sfetch_cancel(sfetch_handle_t request) + ------------------------------------------- + This cancels a request in the next sfetch_dowork() call and invokes the + response callback with (response.failed == true) and (response.finished + == true) to give user-code a chance to do any cleanup work for the + request. If sfetch_cancel() is called for a request that is no longer + alive, nothing bad will happen (the call will simply do nothing). + + void sfetch_pause(sfetch_handle_t request) + ------------------------------------------ + This pauses an active request in the next sfetch_dowork() call and puts + it into the PAUSED state. For all requests in PAUSED state, the response + callback will be called in each call to sfetch_dowork() to give user-code + a chance to CONTINUE the request (by calling sfetch_continue()). Pausing + a request makes sense for dynamic rate-limiting in streaming scenarios + (like video/audio streaming with a fixed number of streaming buffers. As + soon as all available buffers are filled with download data, downloading + more data must be prevented to allow video/audio playback to catch up and + free up empty buffers for new download data. + + void sfetch_continue(sfetch_handle_t request) + --------------------------------------------- + Continues a paused request, counterpart to the sfetch_pause() function. + + void sfetch_bind_buffer(sfetch_handle_t request, sfetch_range_t buffer) + ---------------------------------------------------------------------------------------- + This "binds" a new buffer (as pointer/size pair) to an active request. The + function *must* be called from inside the response-callback, and there + must not already be another buffer bound. + + void* sfetch_unbind_buffer(sfetch_handle_t request) + --------------------------------------------------- + This removes the current buffer binding from the request and returns + a pointer to the previous buffer (useful if the buffer was dynamically + allocated and it must be freed). + + sfetch_unbind_buffer() *must* be called from inside the response callback. + + The usual code sequence to bind a different buffer in the response + callback might look like this: + + void response_callback(const sfetch_response_t* response) { + if (response.fetched) { + ... + // switch to a different buffer (in the FETCHED state it is + // guaranteed that the request has a buffer, otherwise it + // would have gone into the FAILED state + void* old_buf_ptr = sfetch_unbind_buffer(response.handle); + free(old_buf_ptr); + void* new_buf_ptr = malloc(new_buf_size); + sfetch_bind_buffer(response.handle, new_buf_ptr, new_buf_size); + } + if (response.finished) { + // unbind and free the currently associated buffer, + // the buffer pointer could be null if the request has failed + // NOTE that it is legal to call free() with a nullptr, + // this happens if the request failed to open its file + // and never goes into the OPENED state + void* buf_ptr = sfetch_unbind_buffer(response.handle); + free(buf_ptr); + } + } + + sfetch_desc_t sfetch_desc(void) + ------------------------------- + sfetch_desc() returns a copy of the sfetch_desc_t struct passed to + sfetch_setup(), with zero-initialized values replaced with + their default values. + + int sfetch_max_userdata_bytes(void) + ----------------------------------- + This returns the value of the SFETCH_MAX_USERDATA_UINT64 config + define, but in number of bytes (so SFETCH_MAX_USERDATA_UINT64*8). + + int sfetch_max_path(void) + ------------------------- + Returns the value of the SFETCH_MAX_PATH config define. + + + REQUEST STATES AND THE RESPONSE CALLBACK + ======================================== + A request goes through a number of states during its lifetime. Depending + on the current state of a request, it will be 'owned' either by the + "user-thread" (where the request was sent) or an IO thread. + + You can think of a request as "ping-ponging" between the IO thread and + user thread, any actual IO work is done on the IO thread, while + invocations of the response-callback happen on the user-thread. + + All state transitions and callback invocations happen inside the + sfetch_dowork() function. + + An active request goes through the following states: + + ALLOCATED (user-thread) + + The request has been allocated in sfetch_send() and is + waiting to be dispatched into its IO channel. When this + happens, the request will transition into the DISPATCHED state. + + DISPATCHED (IO thread) + + The request has been dispatched into its IO channel, and a + lane has been assigned to the request. + + If a buffer was provided in sfetch_send() the request will + immediately transition into the FETCHING state and start loading + data into the buffer. + + If no buffer was provided in sfetch_send(), the response + callback will be called with (response->dispatched == true), + so that the response callback can bind a buffer to the + request. Binding the buffer in the response callback makes + sense if the buffer isn't dynamically allocated, but instead + a pre-allocated buffer must be selected from the request's + channel and lane. + + Note that it isn't possible to get a file size in the response callback + which would help with allocating a buffer of the right size, this is + because it isn't possible in HTTP to query the file size before the + entire file is downloaded (...when the web server serves files compressed). + + If opening the file failed, the request will transition into + the FAILED state with the error code SFETCH_ERROR_FILE_NOT_FOUND. + + FETCHING (IO thread) + + While a request is in the FETCHING state, data will be loaded into + the user-provided buffer. + + If no buffer was provided, the request will go into the FAILED + state with the error code SFETCH_ERROR_NO_BUFFER. + + If a buffer was provided, but it is too small to contain the + fetched data, the request will go into the FAILED state with + error code SFETCH_ERROR_BUFFER_TOO_SMALL. + + If less data can be read from the file than expected, the request + will go into the FAILED state with error code SFETCH_ERROR_UNEXPECTED_EOF. + + If loading data into the provided buffer works as expected, the + request will go into the FETCHED state. + + FETCHED (user thread) + + The request goes into the FETCHED state either when the entire file + has been loaded into the provided buffer (when request.chunk_size == 0), + or a chunk has been loaded (and optionally decompressed) into the + buffer (when request.chunk_size > 0). + + The response callback will be called so that the user-code can + process the loaded data using the following sfetch_response_t struct members: + + - data.ptr: pointer to the start of fetched data + - data.size: the number of bytes in the provided buffer + - data_offset: the byte offset of the loaded data chunk in the + overall file (this is only set to a non-zero value in a streaming + scenario) + + Once all file data has been loaded, the 'finished' flag will be set + in the response callback's sfetch_response_t argument. + + After the user callback returns, and all file data has been loaded + (response.finished flag is set) the request has reached its end-of-life + and will be recycled. + + Otherwise, if there's still data to load (because streaming was + requested by providing a non-zero request.chunk_size), the request + will switch back to the FETCHING state to load the next chunk of data. + + Note that it is ok to associate a different buffer or buffer-size + with the request by calling sfetch_bind_buffer() in the response-callback. + + To check in the response callback for the FETCHED state, and + independently whether the request is finished: + + void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // request is in FETCHED state, the loaded data is available + // in .data.ptr, and the number of bytes that have been + // loaded in .data.size: + const void* data = response->data.ptr; + size_t num_bytes = response->data.size; + } + if (response->finished) { + // the finished flag is set either when all data + // has been loaded, the request has been cancelled, + // or the file operation has failed, this is where + // any required per-request cleanup work should happen + } + } + + + FAILED (user thread) + + A request will transition into the FAILED state in the following situations: + + - if the file doesn't exist or couldn't be opened for other + reasons (SFETCH_ERROR_FILE_NOT_FOUND) + - if no buffer is associated with the request in the FETCHING state + (SFETCH_ERROR_NO_BUFFER) + - if the provided buffer is too small to hold the entire file + (if request.chunk_size == 0), or the (potentially decompressed) + partial data chunk (SFETCH_ERROR_BUFFER_TOO_SMALL) + - if less bytes could be read from the file then expected + (SFETCH_ERROR_UNEXPECTED_EOF) + - if a request has been cancelled via sfetch_cancel() + (SFETCH_ERROR_CANCELLED) + + The response callback will be called once after a request goes into + the FAILED state, with the 'response->finished' and + 'response->failed' flags set to true. + + This gives the user-code a chance to cleanup any resources associated + with the request. + + To check for the failed state in the response callback: + + void response_callback(const sfetch_response_t* response) { + if (response->failed) { + // specifically check for the failed state... + } + // or you can do a catch-all check via the finished-flag: + if (response->finished) { + if (response->failed) { + // if more detailed error handling is needed: + switch (response->error_code) { + ... + } + } + } + } + + PAUSED (user thread) + + A request will transition into the PAUSED state after user-code + calls the function sfetch_pause() on the request's handle. Usually + this happens from within the response-callback in streaming scenarios + when the data streaming needs to wait for a data decoder (like + a video/audio player) to catch up. + + While a request is in PAUSED state, the response-callback will be + called in each sfetch_dowork(), so that the user-code can either + continue the request by calling sfetch_continue(), or cancel + the request by calling sfetch_cancel(). + + When calling sfetch_continue() on a paused request, the request will + transition into the FETCHING state. Otherwise if sfetch_cancel() is + called, the request will switch into the FAILED state. + + To check for the PAUSED state in the response callback: + + void response_callback(const sfetch_response_t* response) { + if (response->paused) { + // we can check here whether the request should + // continue to load data: + if (should_continue(response->handle)) { + sfetch_continue(response->handle); + } + } + } + + + CHUNK SIZE AND HTTP COMPRESSION + =============================== + TL;DR: for streaming scenarios, the provided chunk-size must be smaller + than the provided buffer-size because the web server may decide to + serve the data compressed and the chunk-size must be given in 'compressed + bytes' while the buffer receives 'uncompressed bytes'. It's not possible + in HTTP to query the uncompressed size for a compressed download until + that download has finished. + + With vanilla HTTP, it is not possible to query the actual size of a file + without downloading the entire file first (the Content-Length response + header only provides the compressed size). Furthermore, for HTTP + range-requests, the range is given on the compressed data, not the + uncompressed data. So if the web server decides to serve the data + compressed, the content-length and range-request parameters don't + correspond to the uncompressed data that's arriving in the sokol-fetch + buffers, and there's no way from JS or WASM to either force uncompressed + downloads (e.g. by setting the Accept-Encoding field), or access the + compressed data. + + This has some implications for sokol_fetch.h, most notably that buffers + can't be provided in the exactly right size, because that size can't + be queried from HTTP before the data is actually downloaded. + + When downloading whole files at once, it is basically expected that you + know the maximum files size upfront through other means (for instance + through a separate meta-data-file which contains the file sizes and + other meta-data for each file that needs to be loaded). + + For streaming downloads the situation is a bit more complicated. These + use HTTP range-requests, and those ranges are defined on the (potentially) + compressed data which the JS/WASM side doesn't have access to. However, + the JS/WASM side only ever sees the uncompressed data, and it's not possible + to query the uncompressed size of a range request before that range request + has finished. + + If the provided buffer is too small to contain the uncompressed data, + the request will fail with error code SFETCH_ERROR_BUFFER_TOO_SMALL. + + + CHANNELS AND LANES + ================== + Channels and lanes are (somewhat artificial) concepts to manage + parallelization, prioritization and rate-limiting. + + Channels can be used to parallelize message processing for better 'pipeline + throughput', and to prioritize requests: user-code could reserve one + channel for streaming downloads which need to run in parallel to other + requests, another channel for "regular" downloads and yet another + high-priority channel which would only be used for small files which need + to start loading immediately. + + Each channel comes with its own IO thread and message queues for pumping + messages in and out of the thread. The channel where a request is + processed is selected manually when sending a message: + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = my_response_callback, + .channel = 2 + }); + + The number of channels is configured at startup in sfetch_setup() and + cannot be changed afterwards. + + Channels are completely separate from each other, and a request will + never "hop" from one channel to another. + + Each channel consists of a fixed number of "lanes" for automatic rate + limiting: + + When a request is sent to a channel via sfetch_send(), a "free lane" will + be picked and assigned to the request. The request will occupy this lane + for its entire life time (also while it is paused). If all lanes of a + channel are currently occupied, new requests will wait until a + lane becomes unoccupied. + + Since the number of channels and lanes is known upfront, it is guaranteed + that there will never be more than "num_channels * num_lanes" requests + in flight at any one time. + + This guarantee eliminates unexpected load- and memory-spikes when + many requests are sent in very short time, and it allows to pre-allocate + a fixed number of memory buffers which can be reused for the entire + "lifetime" of a sokol-fetch context. + + In the most simple scenario - when a maximum file size is known - buffers + can be statically allocated like this: + + uint8_t buffer[NUM_CHANNELS][NUM_LANES][MAX_FILE_SIZE]; + + Then in the user callback pick a buffer by channel and lane, + and associate it with the request like this: + + void response_callback(const sfetch_response_t* response) { + if (response->dispatched) { + void* ptr = buffer[response->channel][response->lane]; + sfetch_bind_buffer(response->handle, ptr, MAX_FILE_SIZE); + } + ... + } + + + NOTES ON OPTIMIZING PIPELINE LATENCY AND THROUGHPUT + =================================================== + With the default configuration of 1 channel and 1 lane per channel, + sokol_fetch.h will appear to have a shockingly bad loading performance + if several files are loaded. + + This has two reasons: + + (1) all parallelization when loading data has been disabled. A new + request will only be processed, when the last request has finished. + + (2) every invocation of the response-callback adds one frame of latency + to the request, because callbacks will only be called from within + sfetch_dowork() + + sokol-fetch takes a few shortcuts to improve step (2) and reduce + the 'inherent latency' of a request: + + - if a buffer is provided upfront, the response-callback won't be + called in the DISPATCHED state, but start right with the FETCHED state + where data has already been loaded into the buffer + + - there is no separate CLOSED state where the callback is invoked + separately when loading has finished (or the request has failed), + instead the finished and failed flags will be set as part of + the last FETCHED invocation + + This means providing a big-enough buffer to fit the entire file is the + best case, the response callback will only be called once, ideally in + the next frame (or two calls to sfetch_dowork()). + + If no buffer is provided upfront, one frame of latency is added because + the response callback needs to be invoked in the DISPATCHED state so that + the user code can bind a buffer. + + This means the best case for a request without an upfront-provided + buffer is 2 frames (or 3 calls to sfetch_dowork()). + + That's about what can be done to improve the latency for a single request, + but the really important step is to improve overall throughput. If you + need to load thousands of files you don't want that to be completely + serialized. + + The most important action to increase throughput is to increase the + number of lanes per channel. This defines how many requests can be + 'in flight' on a single channel at the same time. The guiding decision + factor for how many lanes you can "afford" is the memory size you want + to set aside for buffers. Each lane needs its own buffer so that + the data loaded for one request doesn't scribble over the data + loaded for another request. + + Here's a simple example of sending 4 requests without upfront buffer + on a channel with 1, 2 and 4 lanes, each line is one frame: + + 1 LANE (8 frames): + Lane 0: + ------------- + REQ 0 DISPATCHED + REQ 0 FETCHED + REQ 1 DISPATCHED + REQ 1 FETCHED + REQ 2 DISPATCHED + REQ 2 FETCHED + REQ 3 DISPATCHED + REQ 3 FETCHED + + Note how the request don't overlap, so they can all use the same buffer. + + 2 LANES (4 frames): + Lane 0: Lane 1: + ------------------------------------ + REQ 0 DISPATCHED REQ 1 DISPATCHED + REQ 0 FETCHED REQ 1 FETCHED + REQ 2 DISPATCHED REQ 3 DISPATCHED + REQ 2 FETCHED REQ 3 FETCHED + + This reduces the overall time to 4 frames, but now you need 2 buffers so + that requests don't scribble over each other. + + 4 LANES (2 frames): + Lane 0: Lane 1: Lane 2: Lane 3: + ---------------------------------------------------------------------------- + REQ 0 DISPATCHED REQ 1 DISPATCHED REQ 2 DISPATCHED REQ 3 DISPATCHED + REQ 0 FETCHED REQ 1 FETCHED REQ 2 FETCHED REQ 3 FETCHED + + Now we're down to the same 'best-case' latency as sending a single + request. + + Apart from the memory requirements for the streaming buffers (which is + under your control), you can be generous with the number of lanes, + they don't add any processing overhead. + + The last option for tweaking latency and throughput is channels. Each + channel works independently from other channels, so while one + channel is busy working through a large number of requests (or one + very long streaming download), you can set aside a high-priority channel + for requests that need to start as soon as possible. + + On platforms with threading support, each channel runs on its own + thread, but this is mainly an implementation detail to work around + the traditional blocking file IO functions, not for performance reasons. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sfetch_setup(&(sfetch_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_fetch.h + itself though, not any allocations in OS libraries. + + Memory allocation will only happen on the same thread where sfetch_setup() + was called, so you don't need to worry about thread-safety. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sfetch_setup(&(sfetch_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sfetch' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SFETCH_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_fetch.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-fetch like this: + + sfetch_setup(&(sfetch_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + FUTURE PLANS / V2.0 IDEA DUMP + ============================= + - An optional polling API (as alternative to callback API) + - Move buffer-management into the API? The "manual management" + can be quite tricky especially for dynamic allocation scenarios, + API support for buffer management would simplify cases like + preventing that requests scribble over each other's buffers, or + an automatic garbage collection for dynamically allocated buffers, + or automatically falling back to dynamic allocation if static + buffers aren't big enough. + - Pluggable request handlers to load data from other "sources" + (especially HTTP downloads on native platforms via e.g. libcurl + would be useful) + - I'm currently not happy how the user-data block is handled, this + should getting and updating the user-data should be wrapped by + API functions (similar to bind/unbind buffer) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2019 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_fetch_clib :: #library "sokol_fetch_windows_x64_gl_debug"; } + else { sokol_fetch_clib :: #library "sokol_fetch_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_fetch_clib :: #library "sokol_fetch_windows_x64_d3d11_debug"; } + else { sokol_fetch_clib :: #library "sokol_fetch_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_windows_x64_gl_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_windows_x64_d3d11_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_fetch_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_arm64_gl_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_x64_gl_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_arm64_metal_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_x64_metal_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_linux_x64_gl_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_fetch_clib :: #library,no_dll "sokol_fetch_wasm_gl_debug"; } + else { sokol_fetch_clib :: #library,no_dll "sokol_fetch_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// setup sokol-fetch (can be called on multiple threads) +sfetch_setup :: (desc: *sfetch_desc_t) -> void #foreign sokol_fetch_clib; +// discard a sokol-fetch context +sfetch_shutdown :: () -> void #foreign sokol_fetch_clib; +// return true if sokol-fetch has been setup +sfetch_valid :: () -> bool #foreign sokol_fetch_clib; +// get the desc struct that was passed to sfetch_setup() +sfetch_desc :: () -> sfetch_desc_t #foreign sokol_fetch_clib; +// return the max userdata size in number of bytes (SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) +sfetch_max_userdata_bytes :: () -> s32 #foreign sokol_fetch_clib; +// return the value of the SFETCH_MAX_PATH implementation config value +sfetch_max_path :: () -> s32 #foreign sokol_fetch_clib; +// send a fetch-request, get handle to request back +sfetch_send :: (request: *sfetch_request_t) -> sfetch_handle_t #foreign sokol_fetch_clib; +// return true if a handle is valid *and* the request is alive +sfetch_handle_valid :: (h: sfetch_handle_t) -> bool #foreign sokol_fetch_clib; +// do per-frame work, moves requests into and out of IO threads, and invokes response-callbacks +sfetch_dowork :: () -> void #foreign sokol_fetch_clib; +// bind a data buffer to a request (request must not currently have a buffer bound, must be called from response callback +sfetch_bind_buffer :: (h: sfetch_handle_t, buffer: sfetch_range_t) -> void #foreign sokol_fetch_clib; +// clear the 'buffer binding' of a request, returns previous buffer pointer (can be 0), must be called from response callback +sfetch_unbind_buffer :: (h: sfetch_handle_t) -> *void #foreign sokol_fetch_clib; +// cancel a request that's in flight (will call response callback with .cancelled + .finished) +sfetch_cancel :: (h: sfetch_handle_t) -> void #foreign sokol_fetch_clib; +// pause a request (will call response callback each frame with .paused) +sfetch_pause :: (h: sfetch_handle_t) -> void #foreign sokol_fetch_clib; +// continue a paused request +sfetch_continue :: (h: sfetch_handle_t) -> void #foreign sokol_fetch_clib; + +sfetch_log_item_t :: enum u32 { + OK; + MALLOC_FAILED; + FILE_PATH_UTF8_DECODING_FAILED; + SEND_QUEUE_FULL; + REQUEST_CHANNEL_INDEX_TOO_BIG; + REQUEST_PATH_IS_NULL; + REQUEST_PATH_TOO_LONG; + REQUEST_CALLBACK_MISSING; + REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE; + REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL; + REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT; + REQUEST_USERDATA_SIZE_TOO_BIG; + CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS; + REQUEST_POOL_EXHAUSTED; +} + +sfetch_logger_t :: struct { + func : (a0: *u8, a1: u32, a2: u32, a3: *u8, a4: u32, a5: *u8, a6: *void) #c_call; + user_data : *void; +} + +sfetch_range_t :: struct { + ptr : *void; + size : u64; +} + +sfetch_allocator_t :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +sfetch_desc_t :: struct { + max_requests : u32; + num_channels : u32; + num_lanes : u32; + allocator : sfetch_allocator_t; + logger : sfetch_logger_t; +} + +sfetch_handle_t :: struct { + id : u32; +} + +// error codes +sfetch_error_t :: enum u32 { + NO_ERROR; + FILE_NOT_FOUND; + NO_BUFFER; + BUFFER_TOO_SMALL; + UNEXPECTED_EOF; + INVALID_HTTP_STATUS; + CANCELLED; + JS_OTHER; +} + +sfetch_response_t :: struct { + handle : sfetch_handle_t; + dispatched : bool; + fetched : bool; + paused : bool; + finished : bool; + failed : bool; + cancelled : bool; + error_code : sfetch_error_t; + channel : u32; + lane : u32; + path : *u8; + user_data : *void; + data_offset : u32; + data : sfetch_range_t; + buffer : sfetch_range_t; +} + +sfetch_request_t :: struct { + channel : u32; + path : *u8; + callback : (a0: *sfetch_response_t) #c_call; + chunk_size : u32; + buffer : sfetch_range_t; + user_data : sfetch_range_t; +} + diff --git a/modules/sokol-jai/sokol/fontstash/module.jai b/modules/sokol-jai/sokol/fontstash/module.jai new file mode 100644 index 0000000..e5d8a4e --- /dev/null +++ b/modules/sokol-jai/sokol/fontstash/module.jai @@ -0,0 +1,537 @@ +// TODO: generate this automatically + +// ---------------------------------------- + +STBTT_MACSTYLE_DONTCARE :: 0; +STBTT_MACSTYLE_BOLD :: 1; +STBTT_MACSTYLE_ITALIC :: 2; +STBTT_MACSTYLE_UNDERSCORE :: 4; +STBTT_MACSTYLE_NONE :: 8; + +// private structure +stbtt__buf :: struct { + data: *u8; + cursor: s32; + size: s32; +} + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// +stbtt_bakedchar :: struct { + x0: u16; // coordinates of bbox in bitmap + y0: u16; // coordinates of bbox in bitmap + x1: u16; // coordinates of bbox in bitmap + y1: u16; // coordinates of bbox in bitmap + xoff: float; + yoff: float; + xadvance: float; +} + +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. +stbtt_aligned_quad :: struct { + x0: float; // top-left + y0: float; // top-left + s0: float; // top-left + t0: float; // top-left + x1: float; // bottom-right + y1: float; // bottom-right + s1: float; // bottom-right + t1: float; // bottom-right +} + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. +stbtt_packedchar :: struct { + x0: u16; // coordinates of bbox in bitmap + y0: u16; // coordinates of bbox in bitmap + x1: u16; // coordinates of bbox in bitmap + y1: u16; // coordinates of bbox in bitmap + xoff: float; + yoff: float; + xadvance: float; + xoff2: float; + yoff2: float; +} + +// stbrp_rect :: struct {} + +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall +stbtt_pack_range :: struct { + font_size: float; + first_unicode_codepoint_in_range: s32; // if non-zero, then the chars are continuous, and this is the first codepoint + array_of_unicode_codepoints: *s32; // if non-zero, then this is an array of unicode codepoints + num_chars: s32; + chardata_for_range: *stbtt_packedchar; // output + h_oversample: u8; // don't set these, they're used internally + v_oversample: u8; // don't set these, they're used internally +} + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +stbtt_pack_context :: struct { + user_allocator_context: *void; + pack_info: *void; + width: s32; + height: s32; + stride_in_bytes: s32; + padding: s32; + skip_missing: s32; + h_oversample: u32; + v_oversample: u32; + pixels: *u8; + nodes: *void; +} + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +stbtt_fontinfo :: struct { + userdata: *void; + data: *u8; // pointer to .ttf file + fontstart: s32; // offset of start of font + + numGlyphs: s32; // number of glyphs, needed for range checking + + loca: s32; // table locations as offset from start of .ttf + head: s32; // table locations as offset from start of .ttf + glyf: s32; // table locations as offset from start of .ttf + hhea: s32; // table locations as offset from start of .ttf + hmtx: s32; // table locations as offset from start of .ttf + kern: s32; // table locations as offset from start of .ttf + gpos: s32; // table locations as offset from start of .ttf + svg: s32; // table locations as offset from start of .ttf + index_map: s32; // a cmap mapping for our chosen character encoding + indexToLocFormat: s32; // format needed to map from glyph index to glyph + + cff: stbtt__buf; // cff font data + charstrings: stbtt__buf; // the charstring index + gsubrs: stbtt__buf; // global charstring subroutines index + subrs: stbtt__buf; // private charstring subroutines index + fontdicts: stbtt__buf; // array of font dicts + fdselect: stbtt__buf; // map from glyph to fontdict +} + +// as above, but takes one or more glyph indices for greater efficiency +stbtt_kerningentry :: struct { + glyph1: s32; // use stbtt_FindGlyphIndex + glyph2: s32; + advance: s32; +} + +STBTT :: enum u32 { + vmove :: 1; + vline :: 2; + vcurve :: 3; + vcubic :: 4; + + STBTT_vmove :: vmove; + STBTT_vline :: vline; + STBTT_vcurve :: vcurve; + STBTT_vcubic :: vcubic; +} + +stbtt_vertex :: struct { + x: s16; + y: s16; + cx: s16; + cy: s16; + cx1: s16; + cy1: s16; + type: u8; + padding: u8; +} + +// @TODO: don't expose this structure +stbtt__bitmap :: struct { + w: s32; + h: s32; + stride: s32; + pixels: *u8; +} + + +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm +STBTT_PLATFORM_ID :: enum u32 { + UNICODE :: 0; + MAC :: 1; + ISO :: 2; + MICROSOFT :: 3; + + STBTT_PLATFORM_ID_UNICODE :: UNICODE; + STBTT_PLATFORM_ID_MAC :: MAC; + STBTT_PLATFORM_ID_ISO :: ISO; + STBTT_PLATFORM_ID_MICROSOFT :: MICROSOFT; +} + +STBTT_UNICODE_EID :: enum u32 { + UNICODE_1_0 :: 0; + UNICODE_1_1 :: 1; + ISO_10646 :: 2; + UNICODE_2_0_BMP :: 3; + UNICODE_2_0_FULL :: 4; + + STBTT_UNICODE_EID_UNICODE_1_0 :: UNICODE_1_0; + STBTT_UNICODE_EID_UNICODE_1_1 :: UNICODE_1_1; + STBTT_UNICODE_EID_ISO_10646 :: ISO_10646; + STBTT_UNICODE_EID_UNICODE_2_0_BMP :: UNICODE_2_0_BMP; + STBTT_UNICODE_EID_UNICODE_2_0_FULL :: UNICODE_2_0_FULL; +} + +STBTT_MS_EID :: enum u32 { + SYMBOL :: 0; + UNICODE_BMP :: 1; + SHIFTJIS :: 2; + UNICODE_FULL :: 10; + + STBTT_MS_EID_SYMBOL :: SYMBOL; + STBTT_MS_EID_UNICODE_BMP :: UNICODE_BMP; + STBTT_MS_EID_SHIFTJIS :: SHIFTJIS; + STBTT_MS_EID_UNICODE_FULL :: UNICODE_FULL; +} + +STBTT_MAC_EID :: enum u32 { + ROMAN :: 0; + ARABIC :: 4; + JAPANESE :: 1; + HEBREW :: 5; + CHINESE_TRAD :: 2; + GREEK :: 6; + KOREAN :: 3; + RUSSIAN :: 7; + + STBTT_MAC_EID_ROMAN :: ROMAN; + STBTT_MAC_EID_ARABIC :: ARABIC; + STBTT_MAC_EID_JAPANESE :: JAPANESE; + STBTT_MAC_EID_HEBREW :: HEBREW; + STBTT_MAC_EID_CHINESE_TRAD :: CHINESE_TRAD; + STBTT_MAC_EID_GREEK :: GREEK; + STBTT_MAC_EID_KOREAN :: KOREAN; + STBTT_MAC_EID_RUSSIAN :: RUSSIAN; +} + +STBTT_MS_LANG :: enum u32 { + ENGLISH :: 1033; + ITALIAN :: 1040; + CHINESE :: 2052; + JAPANESE :: 1041; + DUTCH :: 1043; + KOREAN :: 1042; + FRENCH :: 1036; + RUSSIAN :: 1049; + GERMAN :: 1031; + SPANISH :: 1033; + HEBREW :: 1037; + SWEDISH :: 1053; + + STBTT_MS_LANG_ENGLISH :: ENGLISH; + STBTT_MS_LANG_ITALIAN :: ITALIAN; + STBTT_MS_LANG_CHINESE :: CHINESE; + STBTT_MS_LANG_JAPANESE :: JAPANESE; + STBTT_MS_LANG_DUTCH :: DUTCH; + STBTT_MS_LANG_KOREAN :: KOREAN; + STBTT_MS_LANG_FRENCH :: FRENCH; + STBTT_MS_LANG_RUSSIAN :: RUSSIAN; + STBTT_MS_LANG_GERMAN :: GERMAN; + STBTT_MS_LANG_SPANISH :: SPANISH; + STBTT_MS_LANG_HEBREW :: HEBREW; + STBTT_MS_LANG_SWEDISH :: SWEDISH; +} + +STBTT_MAC_LANG :: enum u32 { + ENGLISH :: 0; + JAPANESE :: 11; + ARABIC :: 12; + KOREAN :: 23; + DUTCH :: 4; + RUSSIAN :: 32; + FRENCH :: 1; + SPANISH :: 6; + GERMAN :: 2; + SWEDISH :: 5; + HEBREW :: 10; + CHINESE_SIMPLIFIED :: 33; + ITALIAN :: 3; + CHINESE_TRAD :: 19; + + STBTT_MAC_LANG_ENGLISH :: ENGLISH; + STBTT_MAC_LANG_JAPANESE :: JAPANESE; + STBTT_MAC_LANG_ARABIC :: ARABIC; + STBTT_MAC_LANG_KOREAN :: KOREAN; + STBTT_MAC_LANG_DUTCH :: DUTCH; + STBTT_MAC_LANG_RUSSIAN :: RUSSIAN; + STBTT_MAC_LANG_FRENCH :: FRENCH; + STBTT_MAC_LANG_SPANISH :: SPANISH; + STBTT_MAC_LANG_GERMAN :: GERMAN; + STBTT_MAC_LANG_SWEDISH :: SWEDISH; + STBTT_MAC_LANG_HEBREW :: HEBREW; + STBTT_MAC_LANG_CHINESE_SIMPLIFIED :: CHINESE_SIMPLIFIED; + STBTT_MAC_LANG_ITALIAN :: ITALIAN; + STBTT_MAC_LANG_CHINESE_TRAD :: CHINESE_TRAD; +} +// ---------------------------------------- + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_fontstash_clib :: #library "sokol_fontstash_windows_x64_gl_debug"; } + else { sokol_fontstash_clib :: #library "sokol_fontstash_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_fontstash_clib :: #library "sokol_fontstash_windows_x64_d3d11_debug"; } + else { sokol_fontstash_clib :: #library "sokol_fontstash_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_windows_x64_gl_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_windows_x64_d3d11_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_fontstash_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + #if CPU == .ARM64 { + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_arm64_gl_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_x64_gl_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_arm64_metal_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_x64_metal_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #system_library,link_always "m"; + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_linux_x64_gl_debug"; } + sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_linux_x64_gl_release"; +} else #if OS == .WASM { + #if DEBUG { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_wasm_gl_debug"; } + else { sokol_fontstash_clib :: #library,no_dll "sokol_fontstash_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +sfons_allocator_t :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +sfons_desc_t :: struct { + width: s32; // initial width of font atlas texture (default: 512, must be power of 2) + height: s32; // initial height of font atlas texture (default: 512, must be power of 2) + allocator: sfons_allocator_t; // optional memory allocation overrides +} + +FONS_VERTEX_COUNT :: 1024; +FONS_MAX_STATES :: 20; +FONS_MAX_FALLBACKS :: 20; +FONS_HASH_LUT_SIZE :: 256; + +FONSparams :: struct { + width, height: s32; + flags: u8; + userPtr: *void; + renderCreate: (uptr: *void, width: s32, height: s32) -> s32; + renderResize: (uptr: *void, width: s32, height: s32) -> s32; + renderUpdate: (uptr: *void, rect: *s32, data: *u8) -> void; + renderDraw: (uptr: *void, verts: *float, tcoords: *float, colors: *s32, nverts: s32) -> void; + renderDelete: (uptr: *void) -> void; +} + +FONSglyph :: struct { + codepoint: u32; + index: s32; + next: s32; + size, blur: s16; + x0,y0,x1,y1: s16; + xadv,xoff,yoff: s16; +} + +FONSttFontImpl :: struct { + font: stbtt_fontinfo; +} + +FONSfont :: struct { + font: FONSttFontImpl; + name: [64]u8; + data: *u8; + dataSize: s32; + freeData: u8; + ascender: float; + descender: float; + lineh: float; + glyphs: *FONSglyph; + cglyphs: s32; + nglyphs: s32; + lut: [FONS_HASH_LUT_SIZE]s32; + fallbacks: [FONS_MAX_FALLBACKS]s32; + nfallbacks: s32; +} + +FONScontext :: struct { + params: FONSparams; + itw,ith: float; + texData: *u8; + dirtyRect: [4]s32; + fonts: **FONSfont; + atlas: *FONSatlas; + cfonts: s32; + nfonts: s32; + verts: [FONS_VERTEX_COUNT*2]float; + tcoords: [FONS_VERTEX_COUNT*2]float; + colors: [FONS_VERTEX_COUNT]u32; + nverts: s32; + scratch: *u8; + nscratch: s32; + states: [FONS_MAX_STATES]FONSstate; + nstates: s32; + handleError: (uptr: *void, error: s32, val: s32) -> *void; + errorUptr: *void; +} + +FONSstate :: struct { + font: s32; + align: s32; + size: float; + color: u32; + blur: float; + spacing: float; +} + +FONSatlasNode :: struct { + x, y, width: s16; +} + +FONSatlas :: struct +{ + width, height: s32; + nodes: *FONSatlasNode; + nnodes: s32; + cnodes: s32; +} + +using FONSalign :: enum { + // Horizontal align + FONS_ALIGN_LEFT :: 1<<0; // Default + FONS_ALIGN_CENTER :: 1<<1; + FONS_ALIGN_RIGHT :: 1<<2; + // Vertical align + FONS_ALIGN_TOP :: 1<<3; + FONS_ALIGN_MIDDLE :: 1<<4; + FONS_ALIGN_BOTTOM :: 1<<5; + FONS_ALIGN_BASELINE :: 1<<6; // Default +} + +FONS_INVALID :: -1; + +sfons_create :: (desc: *sfons_desc_t) -> *FONScontext #foreign sokol_fontstash_clib; +sfons_destroy :: (ctx: *FONScontext) -> void #foreign sokol_fontstash_clib; +sfons_flush :: (ctx: *FONScontext) -> void #foreign sokol_fontstash_clib; +sfons_rgba :: (r: u8, g: u8, b: u8, a: u8) -> u32 #foreign sokol_fontstash_clib; + +// Contructor and destructor. +fonsCreateInternal :: (params: *FONSparams) -> *FONScontext #foreign sokol_fontstash_clib; +fonsDeleteInternal :: (s: *FONScontext) -> void #foreign sokol_fontstash_clib; + +fonsSetErrorCallback :: (s: *FONScontext, callback: #type (uptr: *void, error: s32, val: s32) -> void #c_call, uptr: *void) -> void #foreign sokol_fontstash_clib; + +// Returns current atlas size. +fonsGetAtlasSize :: (s: *FONScontext, width: *s32, height: *s32) -> void #foreign sokol_fontstash_clib; + +// Expands the atlas size. +fonsExpandAtlas :: (s: *FONScontext, width: s32, height: s32) -> s32 #foreign sokol_fontstash_clib; + +// Resets the whole stash. +fonsResetAtlas :: (stash: *FONScontext, width: s32, height: s32) -> s32 #foreign sokol_fontstash_clib; + +// Add fonts +fonsAddFont :: (s: *FONScontext, name: *u8, path: *u8) -> s32 #foreign sokol_fontstash_clib; +fonsAddFontMem :: (s: *FONScontext, name: *u8, data: *u8, ndata: s32, freeData: s32) -> s32 #foreign sokol_fontstash_clib; +fonsGetFontByName :: (s: *FONScontext, name: *u8) -> s32 #foreign sokol_fontstash_clib; +fonsAddFallbackFont :: (stash: *FONScontext, base: s32, fallback: s32) -> s32 #foreign sokol_fontstash_clib; + +// State handling +fonsPushState :: (s: *FONScontext) -> void #foreign sokol_fontstash_clib; +fonsPopState :: (s: *FONScontext) -> void #foreign sokol_fontstash_clib; +fonsClearState :: (s: *FONScontext) -> void #foreign sokol_fontstash_clib; + +// State setting +fonsSetSize :: (s: *FONScontext, size: float) -> void #foreign sokol_fontstash_clib; +fonsSetColor :: (s: *FONScontext, color: u32) -> void #foreign sokol_fontstash_clib; +fonsSetSpacing :: (s: *FONScontext, spacing: float) -> void #foreign sokol_fontstash_clib; +fonsSetBlur :: (s: *FONScontext, blur: float) -> void #foreign sokol_fontstash_clib; +fonsSetAlign :: (s: *FONScontext, align: s32) -> void #foreign sokol_fontstash_clib; +fonsSetFont :: (s: *FONScontext, font: s32) -> void #foreign sokol_fontstash_clib; + +// Draw text +fonsDrawText :: (s: *FONScontext, x: float, y: float, _string: *u8, end: *u8) -> float #foreign sokol_fontstash_clib; + +// Measure text +fonsTextBounds :: (s: *FONScontext, x: float, y: float, _string: *u8, end: *u8, bounds: *float) -> float #foreign sokol_fontstash_clib; +fonsLineBounds :: (s: *FONScontext, y: float, miny: *float, maxy: *float) -> void #foreign sokol_fontstash_clib; +fonsVertMetrics :: (s: *FONScontext, ascender: *float, descender: *float, lineh: *float) -> void #foreign sokol_fontstash_clib; + +// Text iterator +// fonsTextIterInit :: (stash: *FONScontext, iter: *FONStextIter, x: float, y: float, str: *u8, end: *u8) -> s32 #foreign sokol_fontstash_clib; +// fonsTextIterNext :: (stash: *FONScontext, iter: *FONStextIter, quad: *FONSquad) -> s32 #foreign sokol_fontstash_clib; + +// Pull texture changes +fonsGetTextureData :: (stash: *FONScontext, width: *s32, height: *s32) -> *u8 #foreign sokol_fontstash_clib; +fonsValidateTexture :: (s: *FONScontext, dirty: *s32) -> s32 #foreign sokol_fontstash_clib; + +// Draws the stash texture for debugging +fonsDrawDebug :: (s: *FONScontext, x: float, y: float) -> void #foreign sokol_fontstash_clib; diff --git a/modules/sokol-jai/sokol/gfx/module.jai b/modules/sokol-jai/sokol/gfx/module.jai new file mode 100644 index 0000000..cc6d9a1 --- /dev/null +++ b/modules/sokol-jai/sokol/gfx/module.jai @@ -0,0 +1,4099 @@ +// machine generated, do not edit + +/* + + sokol_gfx.h -- simple 3D API wrapper + + Project URL: https://github.com/floooh/sokol + + Example code: https://github.com/floooh/sokol-samples + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GFX_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + In the same place define one of the following to select the rendering + backend: + #define SOKOL_GLCORE + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_DUMMY_BACKEND + + I.e. for the desktop GL it should look like this: + + #include ... + #include ... + #define SOKOL_IMPL + #define SOKOL_GLCORE + #include "sokol_gfx.h" + + The dummy backend replaces the platform-specific backend code with empty + stub functions. This is useful for writing tests that need to run on the + command line. + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_GFX_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_TRACE_HOOKS - enable trace hook callbacks (search below for TRACE HOOKS) + SOKOL_EXTERNAL_GL_LOADER - indicates that you're using your own GL loader, in this case + sokol_gfx.h will not include any platform GL headers and disable + the integrated Win32 GL loader + + If sokol_gfx.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GFX_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + If you want to compile without deprecated structs and functions, + define: + + SOKOL_NO_DEPRECATED + + Optionally define the following to force debug checks and validations + even in release mode: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + sokol_gfx DOES NOT: + =================== + - create a window, swapchain or the 3D-API context/device, you must do this + before sokol_gfx is initialized, and pass any required information + (like 3D device pointers) to the sokol_gfx initialization call + + - present the rendered frame, how this is done exactly usually depends + on how the window and 3D-API context/device was created + + - provide a unified shader language, instead 3D-API-specific shader + source-code or shader-bytecode must be provided (for the "official" + offline shader cross-compiler / code-generator, see here: + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + + + STEP BY STEP + ============ + --- to initialize sokol_gfx, after creating a window and a 3D-API + context/device, call: + + sg_setup(const sg_desc*) + + Depending on the selected 3D backend, sokol-gfx requires some + information about its runtime environment, like a GPU device pointer, + default swapchain pixel formats and so on. If you are using sokol_app.h + for the window system glue, you can use a helper function provided in + the sokol_glue.h header: + + #include "sokol_gfx.h" + #include "sokol_app.h" + #include "sokol_glue.h" + //... + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + }); + + To get any logging output for errors and from the validation layer, you + need to provide a logging callback. Easiest way is through sokol_log.h: + + #include "sokol_log.h" + //... + sg_setup(&(sg_desc){ + //... + .logger.func = slog_func, + }); + + --- create resource objects (at least buffers, shaders and pipelines, + and optionally images, samplers and render-pass-attachments): + + sg_buffer sg_make_buffer(const sg_buffer_desc*) + sg_image sg_make_image(const sg_image_desc*) + sg_sampler sg_make_sampler(const sg_sampler_desc*) + sg_shader sg_make_shader(const sg_shader_desc*) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc*) + sg_attachments sg_make_attachments(const sg_attachments_desc*) + + --- start a render- or compute-pass: + + sg_begin_pass(const sg_pass* pass); + + Typically, render passes render into an externally provided swapchain which + presents the rendering result on the display. Such a 'swapchain pass' + is started like this: + + sg_begin_pass(&(sg_pass){ .action = { ... }, .swapchain = sglue_swapchain() }) + + ...where .action is an sg_pass_action struct containing actions to be performed + at the start and end of a render pass (such as clearing the render surfaces to + a specific color), and .swapchain is an sg_swapchain struct with all the required + information to render into the swapchain's surfaces. + + To start an 'offscreen render pass' into sokol-gfx image objects, an sg_attachment + object handle is required instead of an sg_swapchain struct. An offscreen + pass is started like this (assuming attachments is an sg_attachments handle): + + sg_begin_pass(&(sg_pass){ .action = { ... }, .attachments = attachments }); + + To start a compute-pass, just set the .compute item to true: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + --- set the pipeline state for the next draw call with: + + sg_apply_pipeline(sg_pipeline pip) + + --- fill an sg_bindings struct with the resource bindings for the next + draw- or dispatch-call (0..N vertex buffers, 0 or 1 index buffer, 0..N images, + samplers and storage-buffers), and call: + + sg_apply_bindings(const sg_bindings* bindings) + + to update the resource bindings. Note that in a compute pass, no vertex- + or index-buffer bindings are allowed and will be rejected by the validation + layer. + + --- optionally update shader uniform data with: + + sg_apply_uniforms(int ub_slot, const sg_range* data) + + Read the section 'UNIFORM DATA LAYOUT' to learn about the expected memory layout + of the uniform data passed into sg_apply_uniforms(). + + --- kick off a draw call with: + + sg_draw(int base_element, int num_elements, int num_instances) + + The sg_draw() function unifies all the different ways to render primitives + in a single call (indexed vs non-indexed rendering, and instanced vs non-instanced + rendering). In case of indexed rendering, base_element and num_element specify + indices in the currently bound index buffer. In case of non-indexed rendering + base_element and num_elements specify vertices in the currently bound + vertex-buffer(s). To perform instanced rendering, the rendering pipeline + must be setup for instancing (see sg_pipeline_desc below), a separate vertex buffer + containing per-instance data must be bound, and the num_instances parameter + must be > 1. + + --- ...or kick of a dispatch call to invoke a compute shader workload: + + sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) + + The dispatch args define the number of 'compute workgroups' processed + by the currently applied compute shader. + + --- finish the current pass with: + + sg_end_pass() + + --- when done with the current frame, call + + sg_commit() + + --- at the end of your program, shutdown sokol_gfx with: + + sg_shutdown() + + --- if you need to destroy resources before sg_shutdown(), call: + + sg_destroy_buffer(sg_buffer buf) + sg_destroy_image(sg_image img) + sg_destroy_sampler(sg_sampler smp) + sg_destroy_shader(sg_shader shd) + sg_destroy_pipeline(sg_pipeline pip) + sg_destroy_attachments(sg_attachments atts) + + --- to set a new viewport rectangle, call: + + sg_apply_viewport(int x, int y, int width, int height, bool origin_top_left) + + ...or if you want to specify the viewport rectangle with float values: + + sg_apply_viewportf(float x, float y, float width, float height, bool origin_top_left) + + --- to set a new scissor rect, call: + + sg_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left) + + ...or with float values: + + sg_apply_scissor_rectf(float x, float y, float width, float height, bool origin_top_left) + + Both sg_apply_viewport() and sg_apply_scissor_rect() must be called + inside a rendering pass (e.g. not in a compute pass, or outside a pass) + + Note that sg_begin_default_pass() and sg_begin_pass() will reset both the + viewport and scissor rectangles to cover the entire framebuffer. + + --- to update (overwrite) the content of buffer and image resources, call: + + sg_update_buffer(sg_buffer buf, const sg_range* data) + sg_update_image(sg_image img, const sg_image_data* data) + + Buffers and images to be updated must have been created with + SG_USAGE_DYNAMIC or SG_USAGE_STREAM. + + Only one update per frame is allowed for buffer and image resources when + using the sg_update_*() functions. The rationale is to have a simple + protection from the CPU scribbling over data the GPU is currently + using, or the CPU having to wait for the GPU + + Buffer and image updates can be partial, as long as a rendering + operation only references the valid (updated) data in the + buffer or image. + + --- to append a chunk of data to a buffer resource, call: + + int sg_append_buffer(sg_buffer buf, const sg_range* data) + + The difference to sg_update_buffer() is that sg_append_buffer() + can be called multiple times per frame to append new data to the + buffer piece by piece, optionally interleaved with draw calls referencing + the previously written data. + + sg_append_buffer() returns a byte offset to the start of the + written data, this offset can be assigned to + sg_bindings.vertex_buffer_offsets[n] or + sg_bindings.index_buffer_offset + + Code example: + + for (...) { + const void* data = ...; + const int num_bytes = ...; + int offset = sg_append_buffer(buf, &(sg_range) { .ptr=data, .size=num_bytes }); + bindings.vertex_buffer_offsets[0] = offset; + sg_apply_pipeline(pip); + sg_apply_bindings(&bindings); + sg_apply_uniforms(...); + sg_draw(...); + } + + A buffer to be used with sg_append_buffer() must have been created + with SG_USAGE_DYNAMIC or SG_USAGE_STREAM. + + If the application appends more data to the buffer then fits into + the buffer, the buffer will go into the "overflow" state for the + rest of the frame. + + Any draw calls attempting to render an overflown buffer will be + silently dropped (in debug mode this will also result in a + validation error). + + You can also check manually if a buffer is in overflow-state by calling + + bool sg_query_buffer_overflow(sg_buffer buf) + + You can manually check to see if an overflow would occur before adding + any data to a buffer by calling + + bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size) + + NOTE: Due to restrictions in underlying 3D-APIs, appended chunks of + data will be 4-byte aligned in the destination buffer. This means + that there will be gaps in index buffers containing 16-bit indices + when the number of indices in a call to sg_append_buffer() is + odd. This isn't a problem when each call to sg_append_buffer() + is associated with one draw call, but will be problematic when + a single indexed draw call spans several appended chunks of indices. + + --- to check at runtime for optional features, limits and pixelformat support, + call: + + sg_features sg_query_features() + sg_limits sg_query_limits() + sg_pixelformat_info sg_query_pixelformat(sg_pixel_format fmt) + + --- if you need to call into the underlying 3D-API directly, you must call: + + sg_reset_state_cache() + + ...before calling sokol_gfx functions again + + --- you can inspect the original sg_desc structure handed to sg_setup() + by calling sg_query_desc(). This will return an sg_desc struct with + the default values patched in instead of any zero-initialized values + + --- you can get a desc struct matching the creation attributes of a + specific resource object via: + + sg_buffer_desc sg_query_buffer_desc(sg_buffer buf) + sg_image_desc sg_query_image_desc(sg_image img) + sg_sampler_desc sg_query_sampler_desc(sg_sampler smp) + sg_shader_desc sq_query_shader_desc(sg_shader shd) + sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip) + sg_attachments_desc sg_query_attachments_desc(sg_attachments atts) + + ...but NOTE that the returned desc structs may be incomplete, only + creation attributes that are kept around internally after resource + creation will be filled in, and in some cases (like shaders) that's + very little. Any missing attributes will be set to zero. The returned + desc structs might still be useful as partial blueprint for creating + similar resources if filled up with the missing attributes. + + Calling the query-desc functions on an invalid resource will return + completely zeroed structs (it makes sense to check the resource state + with sg_query_*_state() first) + + --- you can query the default resource creation parameters through the functions + + sg_buffer_desc sg_query_buffer_defaults(const sg_buffer_desc* desc) + sg_image_desc sg_query_image_defaults(const sg_image_desc* desc) + sg_sampler_desc sg_query_sampler_defaults(const sg_sampler_desc* desc) + sg_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc) + sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc) + sg_attachments_desc sg_query_attachments_defaults(const sg_attachments_desc* desc) + + These functions take a pointer to a desc structure which may contain + zero-initialized items for default values. These zero-init values + will be replaced with their concrete values in the returned desc + struct. + + --- you can inspect various internal resource runtime values via: + + sg_buffer_info sg_query_buffer_info(sg_buffer buf) + sg_image_info sg_query_image_info(sg_image img) + sg_sampler_info sg_query_sampler_info(sg_sampler smp) + sg_shader_info sg_query_shader_info(sg_shader shd) + sg_pipeline_info sg_query_pipeline_info(sg_pipeline pip) + sg_attachments_info sg_query_attachments_info(sg_attachments atts) + + ...please note that the returned info-structs are tied quite closely + to sokol_gfx.h internals, and may change more often than other + public API functions and structs. + + --- you can query frame stats and control stats collection via: + + sg_query_frame_stats() + sg_enable_frame_stats() + sg_disable_frame_stats() + sg_frame_stats_enabled() + + --- you can ask at runtime what backend sokol_gfx.h has been compiled for: + + sg_backend sg_query_backend(void) + + --- call the following helper functions to compute the number of + bytes in a texture row or surface for a specific pixel format. + These functions might be helpful when preparing image data for consumption + by sg_make_image() or sg_update_image(): + + int sg_query_row_pitch(sg_pixel_format fmt, int width, int int row_align_bytes); + int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes); + + Width and height are generally in number pixels, but note that 'row' has different meaning + for uncompressed vs compressed pixel formats: for uncompressed formats, a row is identical + with a single line if pixels, while in compressed formats, one row is a line of *compression blocks*. + + This is why calling sg_query_surface_pitch() for a compressed pixel format and height + N, N+1, N+2, ... may return the same result. + + The row_align_bytes parammeter is for added flexibility. For image data that goes into + the sg_make_image() or sg_update_image() this should generally be 1, because these + functions take tightly packed image data as input no matter what alignment restrictions + exist in the backend 3D APIs. + + ON INITIALIZATION: + ================== + When calling sg_setup(), a pointer to an sg_desc struct must be provided + which contains initialization options. These options provide two types + of information to sokol-gfx: + + (1) upper bounds and limits needed to allocate various internal + data structures: + - the max number of resources of each type that can + be alive at the same time, this is used for allocating + internal pools + - the max overall size of uniform data that can be + updated per frame, including a worst-case alignment + per uniform update (this worst-case alignment is 256 bytes) + - the max size of all dynamic resource updates (sg_update_buffer, + sg_append_buffer and sg_update_image) per frame + - the max number of compute-dispatch calls in a compute pass + Not all of those limit values are used by all backends, but it is + good practice to provide them none-the-less. + + (2) 3D backend "environment information" in a nested sg_environment struct: + - pointers to backend-specific context- or device-objects (for instance + the D3D11, WebGPU or Metal device objects) + - defaults for external swapchain pixel formats and sample counts, + these will be used as default values in image and pipeline objects, + and the sg_swapchain struct passed into sg_begin_pass() + Usually you provide a complete sg_environment struct through + a helper function, as an example look at the sglue_environment() + function in the sokol_glue.h header. + + See the documentation block of the sg_desc struct below for more information. + + + ON RENDER PASSES + ================ + Relevant samples: + - https://floooh.github.io/sokol-html5/offscreen-sapp.html + - https://floooh.github.io/sokol-html5/offscreen-msaa-sapp.html + - https://floooh.github.io/sokol-html5/mrt-sapp.html + - https://floooh.github.io/sokol-html5/mrt-pixelformats-sapp.html + + A render pass groups rendering commands into a set of render target images + (called 'pass attachments'). Render target images can be used in subsequent + passes as textures (it is invalid to use the same image both as render target + and as texture in the same pass). + + The following sokol-gfx functions must only be called inside a render-pass: + + sg_apply_viewport[f] + sg_apply_scissor_rect[f] + sg_draw + + The folling function may be called inside a render- or compute-pass, but + not outside a pass: + + sg_apply_pipeline + sg_apply_bindings + sg_apply_uniforms + + A frame must have at least one 'swapchain render pass' which renders into an + externally provided swapchain provided as an sg_swapchain struct to the + sg_begin_pass() function. If you use sokol_gfx.h together with sokol_app.h, + just call the sglue_swapchain() helper function in sokol_glue.h to + provide the swapchain information. Otherwise the following information + must be provided: + + - the color pixel-format of the swapchain's render surface + - an optional depth/stencil pixel format if the swapchain + has a depth/stencil buffer + - an optional sample-count for MSAA rendering + - NOTE: the above three values can be zero-initialized, in that + case the defaults from the sg_environment struct will be used that + had been passed to the sg_setup() function. + - a number of backend specific objects: + - GL/GLES3: just a GL framebuffer handle + - D3D11: + - an ID3D11RenderTargetView for the rendering surface + - if MSAA is used, an ID3D11RenderTargetView as + MSAA resolve-target + - an optional ID3D11DepthStencilView for the + depth/stencil buffer + - WebGPU + - a WGPUTextureView object for the rendering surface + - if MSAA is used, a WGPUTextureView object as MSAA resolve target + - an optional WGPUTextureView for the + - Metal (NOTE that the roles of provided surfaces is slightly + different in Metal than in D3D11 or WebGPU, notably, the + CAMetalDrawable is either rendered to directly, or serves + as MSAA resolve target): + - a CAMetalDrawable object which is either rendered + into directly, or in case of MSAA rendering, serves + as MSAA-resolve-target + - if MSAA is used, an multisampled MTLTexture where + rendering goes into + - an optional MTLTexture for the depth/stencil buffer + + It's recommended that you create a helper function which returns an + initialized sg_swapchain struct by value. This can then be directly plugged + into the sg_begin_pass function like this: + + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); + + As an example for such a helper function check out the function sglue_swapchain() + in the sokol_glue.h header. + + For offscreen render passes, the render target images used in a render pass + are baked into an immutable sg_attachments object. + + For a simple offscreen scenario with one color-, one depth-stencil-render + target and without multisampling, creating an attachment object looks like this: + + First create two render target images, one with a color pixel format, + and one with the depth- or depth-stencil pixel format. Both images + must have the same dimensions: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 1, + }); + + NOTE: when creating render target images, have in mind that some default values + are aligned with the default environment attributes in the sg_environment struct + that was passed into the sg_setup() call: + + - the default value for sg_image_desc.pixel_format is taken from + sg_environment.defaults.color_format + - the default value for sg_image_desc.sample_count is taken from + sg_environment.defaults.sample_count + - the default value for sg_image_desc.num_mipmaps is always 1 + + Next create an attachments object: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .depth_stencil.image = depth_img, + }); + + This attachments object is then passed into the sg_begin_pass() function + in place of the swapchain struct: + + sg_begin_pass(&(sg_pass){ .attachments = atts }); + + Swapchain and offscreen passes form dependency trees each with a swapchain + pass at the root, offscreen passes as nodes, and render target images as + dependencies between passes. + + sg_pass_action structs are used to define actions that should happen at the + start and end of rendering passes (such as clearing pass attachments to a + specific color or depth-value, or performing an MSAA resolve operation at + the end of a pass). + + A typical sg_pass_action object which clears the color attachment to black + might look like this: + + const sg_pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + This omits the defaults for the color attachment store action, and + the depth-stencil-attachments actions. The same pass action with the + defaults explicitly filled in would look like this: + + const sg_pass_action pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_STORE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + }, + .depth = = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = 1.0f, + }, + .stencil = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = 0 + } + }; + + With the sg_pass object and sg_pass_action struct in place everything + is ready now for the actual render pass: + + Using such this prepared sg_pass_action in a swapchain pass looks like + this: + + sg_begin_pass(&(sg_pass){ + .action = pass_action, + .swapchain = sglue_swapchain() + }); + ... + sg_end_pass(); + + ...of alternatively in one offscreen pass: + + sg_begin_pass(&(sg_pass){ + .action = pass_action, + .attachments = attachments, + }); + ... + sg_end_pass(); + + Offscreen rendering can also go into a mipmap, or a slice/face of + a cube-, array- or 3d-image (which some restrictions, for instance + it's not possible to create a 3D image with a depth/stencil pixel format, + these exceptions are generally caught by the sokol-gfx validation layer). + + The mipmap/slice selection happens at attachments creation time, for instance + to render into mipmap 2 of slice 3 of an array texture: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { + .image = color_img, + .mip_level = 2, + .slice = 3, + }, + .depth_stencil.image = depth_img, + }); + + If MSAA offscreen rendering is desired, the multi-sample rendering result + must be 'resolved' into a separate 'resolve image', before that image can + be used as texture. + + Creating a simple attachments object for multisampled rendering requires + 3 attachment images: the color attachment image which has a sample + count > 1, a resolve attachment image of the same size and pixel format + but a sample count == 1, and a depth/stencil attachment image with + the same size and sample count as the color attachment image: + + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .sample_count = 1, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 256, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 4, + }); + + ...create the attachments object: + + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .resolves[0].image = resolve_img, + .depth_stencil.image = depth_img, + }); + + If an attachments object defines a resolve image in a specific resolve attachment slot, + an 'msaa resolve operation' will happen in sg_end_pass(). + + In this scenario, the content of the MSAA color attachment doesn't need to be + preserved (since it's only needed inside sg_end_pass for the msaa-resolve), so + the .store_action should be set to "don't care": + + const sg_pass_action = { + .colors[0] = { + .load_action = SG_LOADACTION_CLEAR, + .store_action = SG_STOREACTION_DONTCARE, + .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + The actual render pass looks as usual: + + sg_begin_pass(&(sg_pass){ .action = pass_action, .attachments = atts }); + ... + sg_end_pass(); + + ...after sg_end_pass() the only difference to the non-msaa scenario is that the + rendering result which is going to be used as texture in a followup pass is + in 'resolve_img', not in 'color_img' (in fact, trying to bind color_img as a + texture would result in a validation error). + + + ON COMPUTE PASSES + ================= + Compute passes are used to update the content of storage resources + (currently only storage buffers) by running compute shader code on + the GPU. This will almost always be more efficient than computing + that same data on the CPU and uploading the data via `sg_update_buffer()`. + + NOTE: compute passes are only supported on the following platforms and + backends: + + - macOS and iOS with Metal + - Windows with D3D11 and OpenGL + - Linux with OpenGL or GLES3.1+ + - Web with WebGPU + - Android with GLES3.1+ + + ...this means compute shaders can't be used on the following platform/backend + combos (the same restrictions apply to using storage buffers without compute + shaders): + + - macOS with GL + - iOS with GLES3 + - Web with WebGL2 + + A compute pass is started with: + + sg_begin_pass(&(sg_pass){ .compute = true }); + + ...and finished with: + + sg_end_pass(); + + Typically the following functions will be called inside a compute pass: + + sg_apply_pipeline + sg_apply_bindings + sg_apply_uniforms + sg_dispatch + + The following functions are disallowed inside a compute pass + and will cause validation layer errors: + + sg_apply_viewport[f] + sg_apply_scissor_rect[f] + sg_draw + + Only special 'compute shaders' and 'compute pipelines' can be used in + compute passes. A compute shader only has a compute-function instead + of a vertex- and fragment-function pair, and it doesn't accept vertex- + and index-buffers as input, only storage-buffers, textures and non-filtering + samplers (more details on compute shaders in the following section). + + A compute pipeline is created by providing a compute shader object, + setting the .compute creation parameter to true and not defining any + 'render state': + + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .compute = true, + .shader = compute_shader, + }); + + The sg_apply_bindings and sg_apply_uniforms calls are the same as in + render passes, with the exception that no vertex- and index-buffers + can be bound in the sg_apply_bindings call. + + Finally to kick off a compute workload, call sg_dispatch with the + number of workgroups in the x, y and z-dimension: + + sg_dispatch(int num_groups_x, int num_groups_y, int num_groups_z) + + Also see the following compute-shader samples: + + - https://floooh.github.io/sokol-webgpu/instancing-compute-sapp.html + - https://floooh.github.io/sokol-webgpu/computeboids-sapp.html + + + ON SHADER CREATION + ================== + sokol-gfx doesn't come with an integrated shader cross-compiler, instead + backend-specific shader sources or binary blobs need to be provided when + creating a shader object, along with reflection information about the + shader resource binding interface needed to bind sokol-gfx resources to the + proper shader inputs. + + The easiest way to provide all this shader creation data is to use the + sokol-shdc shader compiler tool to compile shaders from a common + GLSL syntax into backend-specific sources or binary blobs, along with + shader interface information and uniform blocks mapped to C structs. + + To create a shader using a C header which has been code-generated by sokol-shdc: + + // include the C header code-generated by sokol-shdc: + #include "myshader.glsl.h" + ... + + // create shader using a code-generated helper function from the C header: + sg_shader shd = sg_make_shader(myshader_shader_desc(sg_query_backend())); + + The samples in the 'sapp' subdirectory of the sokol-samples project + also use the sokol-shdc approach: + + https://github.com/floooh/sokol-samples/tree/master/sapp + + If you're planning to use sokol-shdc, you can stop reading here, instead + continue with the sokol-shdc documentation: + + https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md + + To create shaders with backend-specific shader code or binary blobs, + the sg_make_shader() function requires the following information: + + - Shader code or shader binary blobs for the vertex- and fragment-, or the + compute-shader-stage: + - for the desktop GL backend, source code can be provided in '#version 410' or + '#version 430', version 430 is required when using storage buffers and + compute shaders support, but note that this is not available on macOS + - for the GLES3 backend, source code must be provided in '#version 300 es' syntax + - for the D3D11 backend, shaders can be provided as source or binary + blobs, the source code should be in HLSL4.0 (for compatibility with old + low-end GPUs) or preferrably in HLSL5.0 syntax, note that when + shader source code is provided for the D3D11 backend, sokol-gfx will + dynamically load 'd3dcompiler_47.dll' + - for the Metal backends, shaders can be provided as source or binary blobs, the + MSL version should be in 'metal-1.1' (other versions may work but are not tested) + - for the WebGPU backend, shaders must be provided as WGSL source code + - optionally the following shader-code related attributes can be provided: + - an entry function name (only on D3D11 or Metal, but not OpenGL) + - on D3D11 only, a compilation target (default is "vs_4_0" and "ps_4_0") + + - Information about the input vertex attributes used by the vertex shader, + most of that backend-specific: + - An optional 'base type' (float, signed-/unsigned-int) for each vertex + attribute. When provided, this used by the validation layer to check + that the CPU-side input vertex format is compatible with the input + vertex declaration of the vertex shader. + - Metal: no location information needed since vertex attributes are always bound + by their attribute location defined in the shader via '[[attribute(N)]]' + - WebGPU: no location information needed since vertex attributes are always + bound by their attribute location defined in the shader via `@location(N)` + - GLSL: vertex attribute names can be optionally provided, in that case their + location will be looked up by name, otherwise, the vertex attribute location + can be defined with 'layout(location = N)' + - D3D11: a 'semantic name' and 'semantic index' must be provided for each vertex + attribute, e.g. if the vertex attribute is defined as 'TEXCOORD1' in the shader, + the semantic name would be 'TEXCOORD', and the semantic index would be '1' + + NOTE that vertex attributes currently must not have gaps. This requirement + may be relaxed in the future. + + - Specifically for Metal compute shaders, the 'number of threads per threadgroup' + must be provided. Normally this is extracted by sokol-shdc from the GLSL + shader source code. For instance the following statement in the input + GLSL: + + layout(local_size_x=64, local_size_y=1, local_size_z=1) in; + + ...will be communicated to the sokol-gfx Metal backend in the + code-generated sg_shader_desc struct: + + (sg_shader_desc){ + .mtl_threads_per_threadgroup = { .x = 64, .y = 1, .z = 1 }, + } + + - Information about each uniform block used in the shader: + - the shader stage of the uniform block (vertex, fragment or compute) + - the size of the uniform block in number of bytes + - a memory layout hint (currently 'native' or 'std140') where 'native' defines a + backend-specific memory layout which shouldn't be used for cross-platform code. + Only std140 guarantees a backend-agnostic memory layout. + - a backend-specific bind slot: + - D3D11/HLSL: the buffer register N (`register(bN)`) where N is 0..7 + - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 0..7 + - WebGPU: the binding N in `@group(0) @binding(N)` where N is 0..15 + - For GLSL only: a description of the internal uniform block layout, which maps + member types and their offsets on the CPU side to uniform variable names + in the GLSL shader + - please also NOTE the documentation sections about UNIFORM DATA LAYOUT + and CROSS-BACKEND COMMON UNIFORM DATA LAYOUT below! + + - A description of each storage buffer used in the shader: + - the shader stage of the storage buffer + - a boolean 'readonly' flag, this is used for validation and hazard + tracking in some 3D backends. Note that in render passes, only + readonly storage buffer bindings are allowed. In compute passes, any + read/write storage buffer binding is assumbed to be written to by the + compute shader. + - a backend-specific bind slot: + - D3D11/HLSL: + - for readonly storage buffer bindings: the texture register N + (`register(tN)`) where N is 0..23 (in HLSL, readonly storage + buffers and textures share the same bind space for + 'shader resource views') + - for read/write storage buffer buffer bindings: the UAV register N + (`register(uN)`) where N is 0..7 (in HLSL, readwrite storage + buffers use their own bind space for 'unordered access views') + - Metal/MSL: the buffer bind slot N (`[[buffer(N)]]`) where N is 8..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + - GL/GLSL: the buffer binding N in `layout(binding=N)` where N is 0..7 + - note that storage buffers are not supported on all backends + and platforms + + - A description of each texture/image used in the shader: + - the shader stage of the texture (vertex, fragment or compute) + - the expected image type: + - SG_IMAGETYPE_2D + - SG_IMAGETYPE_CUBE + - SG_IMAGETYPE_3D + - SG_IMAGETYPE_ARRAY + - the expected 'image sample type': + - SG_IMAGESAMPLETYPE_FLOAT + - SG_IMAGESAMPLETYPE_DEPTH + - SG_IMAGESAMPLETYPE_SINT + - SG_IMAGESAMPLETYPE_UINT + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT + - a flag whether the texture is expected to be multisampled + - a backend-specific bind slot: + - D3D11/HLSL: the texture register N (`register(tN)`) where N is 0..23 + (in HLSL, readonly storage buffers and texture share the same bind space) + - Metal/MSL: the texture bind slot N (`[[texture(N)]]`) where N is 0..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + - A description of each sampler used in the shader: + - the shader stage of the sampler (vertex, fragment or compute) + - the expected sampler type: + - SG_SAMPLERTYPE_FILTERING, + - SG_SAMPLERTYPE_NONFILTERING, + - SG_SAMPLERTYPE_COMPARISON, + - a backend-specific bind slot: + - D3D11/HLSL: the sampler register N (`register(sN)`) where N is 0..15 + - Metal/MSL: the sampler bind slot N (`[[sampler(N)]]`) where N is 0..15 + - WebGPU/WGSL: the binding N in `@group(0) @binding(N)` where N is 0..127 + + - An array of 'image-sampler-pairs' used by the shader to sample textures, + for D3D11, Metal and WebGPU this is used for validation purposes to check + whether the texture and sampler are compatible with each other (especially + WebGPU is very picky about combining the correct + texture-sample-type with the correct sampler-type). For GLSL an + additional 'combined-image-sampler name' must be provided because 'OpenGL + style GLSL' cannot handle separate texture and sampler objects, but still + groups them into a traditional GLSL 'sampler object'. + + Compatibility rules for image-sample-type vs sampler-type are as follows: + + - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING) + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON + + Backend-specific bindslot ranges (not relevant when using sokol-shdc): + + - D3D11/HLSL: + - separate bindslot space per shader stage + - uniform blocks (as cbuffer): `register(b0..b7)` + - textures and readonly storage buffers: `register(t0..t23)` + - read/write storage buffers: `register(u0..u7)` + - samplers: `register(s0..s15)` + - Metal/MSL: + - separate bindslot space per shader stage + - uniform blocks: `[[buffer(0..7)]]` + - storage buffers: `[[buffer(8..15)]]` + - textures: `[[texture(0..15)]]` + - samplers: `[[sampler(0..15)]]` + - WebGPU/WGSL: + - common bindslot space across shader stages + - uniform blocks: `@group(0) @binding(0..15)` + - textures, samplers and storage buffers: `@group(1) @binding(0..127)` + - GL/GLSL: + - uniforms and image-samplers are bound by name + - storage buffers: `layout(std430, binding=0..7)` (common + bindslot space across shader stages) + + For example code of how to create backend-specific shader objects, + please refer to the following samples: + + - for D3D11: https://github.com/floooh/sokol-samples/tree/master/d3d11 + - for Metal: https://github.com/floooh/sokol-samples/tree/master/metal + - for OpenGL: https://github.com/floooh/sokol-samples/tree/master/glfw + - for GLES3: https://github.com/floooh/sokol-samples/tree/master/html5 + - for WebGPI: https://github.com/floooh/sokol-samples/tree/master/wgpu + + + ON SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT AND SG_SAMPLERTYPE_NONFILTERING + ======================================================================== + The WebGPU backend introduces the concept of 'unfilterable-float' textures, + which can only be combined with 'nonfiltering' samplers (this is a restriction + specific to WebGPU, but since the same sokol-gfx code should work across + all backend, the sokol-gfx validation layer also enforces this restriction + - the alternative would be undefined behaviour in some backend APIs on + some devices). + + The background is that some mobile devices (most notably iOS devices) can + not perform linear filtering when sampling textures with certain pixel + formats, most notable the 32F formats: + + - SG_PIXELFORMAT_R32F + - SG_PIXELFORMAT_RG32F + - SG_PIXELFORMAT_RGBA32F + + The information of whether a shader is going to be used with such an + unfilterable-float texture must already be provided in the sg_shader_desc + struct when creating the shader (see the above section "ON SHADER CREATION"). + + If you are using the sokol-shdc shader compiler, the information whether a + texture/sampler binding expects an 'unfilterable-float/nonfiltering' + texture/sampler combination cannot be inferred from the shader source + alone, you'll need to provide this hint via annotation-tags. For instance + here is an example from the ozz-skin-sapp.c sample shader which samples an + RGBA32F texture with skinning matrices in the vertex shader: + + ```glsl + @image_sample_type joint_tex unfilterable_float + uniform texture2D joint_tex; + @sampler_type smp nonfiltering + uniform sampler smp; + ``` + + This will result in SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT and + SG_SAMPLERTYPE_NONFILTERING being written to the code-generated + sg_shader_desc struct. + + + ON VERTEX FORMATS + ================= + Sokol-gfx implements the same strict mapping rules from CPU-side + vertex component formats to GPU-side vertex input data types: + + - float and packed normalized CPU-side formats must be used as + floating point base type in the vertex shader + - packed signed-integer CPU-side formats must be used as signed + integer base type in the vertex shader + - packed unsigned-integer CPU-side formats must be used as unsigned + integer base type in the vertex shader + + These mapping rules are enforced by the sokol-gfx validation layer, + but only when sufficient reflection information is provided in + `sg_shader_desc.attrs[].base_type`. This is the case when sokol-shdc + is used, otherwise the default base_type will be SG_SHADERATTRBASETYPE_UNDEFINED + which causes the sokol-gfx validation check to be skipped (of course you + can also provide the per-attribute base type information manually when + not using sokol-shdc). + + The detailed mapping rules from SG_VERTEXFORMAT_* to GLSL data types + are as follows: + + - FLOAT[*] => float, vec* + - BYTE4N => vec* (scaled to -1.0 .. +1.0) + - UBYTE4N => vec* (scaled to 0.0 .. +1.0) + - SHORT[*]N => vec* (scaled to -1.0 .. +1.0) + - USHORT[*]N => vec* (scaled to 0.0 .. +1.0) + - INT[*] => int, ivec* + - UINT[*] => uint, uvec* + - BYTE4 => int* + - UBYTE4 => uint* + - SHORT[*] => int* + - USHORT[*] => uint* + + NOTE that sokol-gfx only provides vertex formats with sizes of a multiple + of 4 (e.g. BYTE4N but not BYTE2N). This is because vertex components must + be 4-byte aligned anyway. + + + UNIFORM DATA LAYOUT: + ==================== + NOTE: if you use the sokol-shdc shader compiler tool, you don't need to worry + about the following details. + + The data that's passed into the sg_apply_uniforms() function must adhere to + specific layout rules so that the GPU shader finds the uniform block + items at the right offset. + + For the D3D11 and Metal backends, sokol-gfx only cares about the size of uniform + blocks, but not about the internal layout. The data will just be copied into + a uniform/constant buffer in a single operation and it's up you to arrange the + CPU-side layout so that it matches the GPU side layout. This also means that with + the D3D11 and Metal backends you are not limited to a 'cross-platform' subset + of uniform variable types. + + If you ever only use one of the D3D11, Metal *or* WebGPU backend, you can stop reading here. + + For the GL backends, the internal layout of uniform blocks matters though, + and you are limited to a small number of uniform variable types. This is + because sokol-gfx must be able to locate the uniform block members in order + to upload them to the GPU with glUniformXXX() calls. + + To describe the uniform block layout to sokol-gfx, the following information + must be passed to the sg_make_shader() call in the sg_shader_desc struct: + + - a hint about the used packing rule (either SG_UNIFORMLAYOUT_NATIVE or + SG_UNIFORMLAYOUT_STD140) + - a list of the uniform block members types in the correct order they + appear on the CPU side + + For example if the GLSL shader has the following uniform declarations: + + uniform mat4 mvp; + uniform vec2 offset0; + uniform vec2 offset1; + uniform vec2 offset2; + + ...and on the CPU side, there's a similar C struct: + + typedef struct { + float mvp[16]; + float offset0[2]; + float offset1[2]; + float offset2[2]; + } params_t; + + ...the uniform block description in the sg_shader_desc must look like this: + + sg_shader_desc desc = { + .vs.uniform_blocks[0] = { + .size = sizeof(params_t), + .layout = SG_UNIFORMLAYOUT_NATIVE, // this is the default and can be omitted + .uniforms = { + // order must be the same as in 'params_t': + [0] = { .name = "mvp", .type = SG_UNIFORMTYPE_MAT4 }, + [1] = { .name = "offset0", .type = SG_UNIFORMTYPE_VEC2 }, + [2] = { .name = "offset1", .type = SG_UNIFORMTYPE_VEC2 }, + [3] = { .name = "offset2", .type = SG_UNIFORMTYPE_VEC2 }, + } + } + }; + + With this information sokol-gfx can now compute the correct offsets of the data items + within the uniform block struct. + + The SG_UNIFORMLAYOUT_NATIVE packing rule works fine if only the GL backends are used, + but for proper D3D11/Metal/GL a subset of the std140 layout must be used which is + described in the next section: + + + CROSS-BACKEND COMMON UNIFORM DATA LAYOUT + ======================================== + For cross-platform / cross-3D-backend code it is important that the same uniform block + layout on the CPU side can be used for all sokol-gfx backends. To achieve this, + a common subset of the std140 layout must be used: + + - The uniform block layout hint in sg_shader_desc must be explicitly set to + SG_UNIFORMLAYOUT_STD140. + - Only the following GLSL uniform types can be used (with their associated sokol-gfx enums): + - float => SG_UNIFORMTYPE_FLOAT + - vec2 => SG_UNIFORMTYPE_FLOAT2 + - vec3 => SG_UNIFORMTYPE_FLOAT3 + - vec4 => SG_UNIFORMTYPE_FLOAT4 + - int => SG_UNIFORMTYPE_INT + - ivec2 => SG_UNIFORMTYPE_INT2 + - ivec3 => SG_UNIFORMTYPE_INT3 + - ivec4 => SG_UNIFORMTYPE_INT4 + - mat4 => SG_UNIFORMTYPE_MAT4 + - Alignment for those types must be as follows (in bytes): + - float => 4 + - vec2 => 8 + - vec3 => 16 + - vec4 => 16 + - int => 4 + - ivec2 => 8 + - ivec3 => 16 + - ivec4 => 16 + - mat4 => 16 + - Arrays are only allowed for the following types: vec4, int4, mat4. + + Note that the HLSL cbuffer layout rules are slightly different from the + std140 layout rules, this means that the cbuffer declarations in HLSL code + must be tweaked so that the layout is compatible with std140. + + The by far easiest way to tackle the common uniform block layout problem is + to use the sokol-shdc shader cross-compiler tool! + + + ON STORAGE BUFFERS + ================== + The two main purpose of storage buffers are: + + - to be populated by compute shaders with dynamically generated data + - for providing random-access data to all shader stages + + Storage buffers can be used to pass large amounts of random access structured + data from the CPU side to the shaders. They are similar to data textures, but are + more convenient to use both on the CPU and shader side since they can be accessed + in shaders as as a 1-dimensional array of struct items. + + Storage buffers are *NOT* supported on the following platform/backend combos: + + - macOS+GL (because storage buffers require GL 4.3, while macOS only goes up to GL 4.1) + - platforms which only support a GLES3.0 context (WebGL2 and iOS) + + To use storage buffers, the following steps are required: + + - write a shader which uses storage buffers (vertex- and fragment-shaders + can only read from storage buffers, while compute-shaders can both read + and write storage buffers) + - create one or more storage buffers via sg_make_buffer() with the + buffer type SG_BUFFERTYPE_STORAGEBUFFER + - when creating a shader via sg_make_shader(), populate the sg_shader_desc + struct with binding info (when using sokol-shdc, this step will be taken care + of automatically) + - which storage buffer bind slots on the vertex-, fragment- or compute-stage + are occupied + - whether the storage buffer on that bind slot is readonly (readonly + bindings are required for vertex- and fragment-shaders, and in compute + shaders the readonly flag is used to control hazard tracking in some + 3D backends) + + - when calling sg_apply_bindings(), apply the matching bind slots with the previously + created storage buffers + - ...and that's it. + + For more details, see the following backend-agnostic sokol samples: + + - simple vertex pulling from a storage buffer: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/vertexpull-sapp.glsl + - instanced rendering via storage buffers (vertex- and instance-pulling): + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-pull-sapp.glsl + - storage buffers both on the vertex- and fragment-stage: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/sbuftex-sapp.glsl + - the Ozz animation sample rewritten to pull all rendering data from storage buffers: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.cc + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/ozz-storagebuffer-sapp.glsl + - the instancing sample modified to use compute shaders: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/instancing-compute-sapp.glsl + - the Compute Boids sample ported to sokol-gfx: + - C code: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.c + - shader: https://github.com/floooh/sokol-samples/blob/master/sapp/computeboids-sapp.glsl + + ...also see the following backend-specific vertex pulling samples (those also don't use sokol-shdc): + + - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/vertexpulling-d3d11.c + - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/vertexpulling-glfw.c + - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/vertexpulling-metal.c + - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/vertexpulling-wgpu.c + + ...and the backend specific compute shader samples: + + - D3D11: https://github.com/floooh/sokol-samples/blob/master/d3d11/instancing-compute-d3d11.c + - desktop GL: https://github.com/floooh/sokol-samples/blob/master/glfw/instancing-compute-glfw.c + - Metal: https://github.com/floooh/sokol-samples/blob/master/metal/instancing-compute-metal.c + - WebGPU: https://github.com/floooh/sokol-samples/blob/master/wgpu/instancing-compute-wgpu.c + + Storage buffer shader authoring caveats when using sokol-shdc: + + - declare a read-only storage buffer interface block with `layout(binding=N) readonly buffer [name] { ... }` + (where 'N' is the index in `sg_bindings.storage_buffers[N]`) + - ...or a read/write storage buffer interface block with `layout(binding=N) buffer [name] { ... }` + - declare a struct which describes a single array item in the storage buffer interface block + - only put a single flexible array member into the storage buffer interface block + + E.g. a complete example in 'sokol-shdc GLSL': + + ```glsl + @vs + // declare a struct: + struct sb_vertex { + vec3 pos; + vec4 color; + } + // declare a buffer interface block with a single flexible struct array: + layout(binding=0) readonly buffer vertices { + sb_vertex vtx[]; + } + // in the shader function, access the storage buffer like this: + void main() { + vec3 pos = vtx[gl_VertexIndex].pos; + ... + } + @end + ``` + + In a compute shader you can read and write the same item in the same + storage buffer (but you'll have to be careful for random access since + many threads of the same compute function run in parallel): + + @cs + struct sb_item { + vec3 pos; + vec3 vel; + } + layout(binding=0) buffer items_ssbo { + sb_item items[]; + } + layout(local_size_x=64, local_size_y=1, local_size_z=1) in; + void main() { + uint idx = gl_GlobalInvocationID.x; + vec3 pos = items[idx].pos; + ... + items[idx].pos = pos; + } + @end + + Backend-specific storage-buffer caveats (not relevant when using sokol-shdc): + + D3D11: + - storage buffers are created as 'raw' Byte Address Buffers + (https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-intro#raw-views-of-buffers) + - in HLSL, use a ByteAddressBuffer for readonly access of the buffer content: + (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-byteaddressbuffer) + - ...or RWByteAddressBuffer for read/write access: + (https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwbyteaddressbuffer) + - readonly-storage buffers and textures are both bound as 'shader-resource-view' and + share the same bind slots (declared as `register(tN)` in HLSL), where N must be in the range 0..23) + - read/write storage buffers are bound as 'unordered-access-view' (declared as `register(uN)` in HLSL + where N is in the range 0..7) + + Metal: + - in Metal there is no internal difference between vertex-, uniform- and + storage-buffers, all are bound to the same 'buffer bind slots' with the + following reserved ranges: + - vertex shader stage: + - uniform buffers: slots 0..7 + - storage buffers: slots 8..15 + - vertex buffers: slots 15..23 + - fragment shader stage: + - uniform buffers: slots 0..7 + - storage buffers: slots 8..15 + - this means in MSL, storage buffer bindings start at [[buffer(8)]] both in + the vertex and fragment stage + + GL: + - the GL backend doesn't use name-lookup to find storage buffer bindings, this + means you must annotate buffers with `layout(std430, binding=N)` in GLSL + - ...where N is 0..7 in the vertex shader, and 8..15 in the fragment shader + + WebGPU: + - in WGSL, textures, samplers and storage buffers all use a shared + bindspace across all shader stages on bindgroup 1: + + `@group(1) @binding(0..127) + + + TRACE HOOKS: + ============ + sokol_gfx.h optionally allows to install "trace hook" callbacks for + each public API functions. When a public API function is called, and + a trace hook callback has been installed for this function, the + callback will be invoked with the parameters and result of the function. + This is useful for things like debugging- and profiling-tools, or + keeping track of resource creation and destruction. + + To use the trace hook feature: + + --- Define SOKOL_TRACE_HOOKS before including the implementation. + + --- Setup an sg_trace_hooks structure with your callback function + pointers (keep all function pointers you're not interested + in zero-initialized), optionally set the user_data member + in the sg_trace_hooks struct. + + --- Install the trace hooks by calling sg_install_trace_hooks(), + the return value of this function is another sg_trace_hooks + struct which contains the previously set of trace hooks. + You should keep this struct around, and call those previous + functions pointers from your own trace callbacks for proper + chaining. + + As an example of how trace hooks are used, have a look at the + imgui/sokol_gfx_imgui.h header which implements a realtime + debugging UI for sokol_gfx.h on top of Dear ImGui. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sg_setup(&(sg_desc){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_gfx.h + itself though, not any allocations in OS libraries. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sg_setup(&(sg_desc){ .logger.func = slog_func }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sg' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SG_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gfx.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gfx like this: + + sg_setup(&(sg_desc){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + COMMIT LISTENERS + ================ + It's possible to hook callback functions into sokol-gfx which are called from + inside sg_commit() in unspecified order. This is mainly useful for libraries + that build on top of sokol_gfx.h to be notified about the end/start of a frame. + + To add a commit listener, call: + + static void my_commit_listener(void* user_data) { + ... + } + + bool success = sg_add_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + The function returns false if the internal array of commit listeners is full, + or the same commit listener had already been added. + + If the function returns true, my_commit_listener() will be called each frame + from inside sg_commit(). + + By default, 1024 distinct commit listeners can be added, but this number + can be tweaked in the sg_setup() call: + + sg_setup(&(sg_desc){ + .max_commit_listeners = 2048, + }); + + An sg_commit_listener item is equal to another if both the function + pointer and user_data field are equal. + + To remove a commit listener: + + bool success = sg_remove_commit_listener((sg_commit_listener){ + .func = my_commit_listener, + .user_data = ..., + }); + + ...where the .func and .user_data field are equal to a previous + sg_add_commit_listener() call. The function returns true if the commit + listener item was found and removed, and false otherwise. + + + RESOURCE CREATION AND DESTRUCTION IN DETAIL + =========================================== + The 'vanilla' way to create resource objects is with the 'make functions': + + sg_buffer sg_make_buffer(const sg_buffer_desc* desc) + sg_image sg_make_image(const sg_image_desc* desc) + sg_sampler sg_make_sampler(const sg_sampler_desc* desc) + sg_shader sg_make_shader(const sg_shader_desc* desc) + sg_pipeline sg_make_pipeline(const sg_pipeline_desc* desc) + sg_attachments sg_make_attachments(const sg_attachments_desc* desc) + + This will result in one of three cases: + + 1. The returned handle is invalid. This happens when there are no more + free slots in the resource pool for this resource type. An invalid + handle is associated with the INVALID resource state, for instance: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID) { + // buffer pool is exhausted + } + + 2. The returned handle is valid, but creating the underlying resource + has failed for some reason. This results in a resource object in the + FAILED state. The reason *why* resource creation has failed differ + by resource type. Look for log messages with more details. A failed + resource state can be checked with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED) { + // creating the resource has failed + } + + 3. And finally, if everything goes right, the returned resource is + in resource state VALID and ready to use. This can be checked + with: + + sg_buffer buf = sg_make_buffer(...) + if (sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID) { + // creating the resource has failed + } + + When calling the 'make functions', the created resource goes through a number + of states: + + - INITIAL: the resource slot associated with the new resource is currently + free (technically, there is no resource yet, just an empty pool slot) + - ALLOC: a handle for the new resource has been allocated, this just means + a pool slot has been reserved. + - VALID or FAILED: in VALID state any 3D API backend resource objects have + been successfully created, otherwise if anything went wrong, the resource + will be in FAILED state. + + Sometimes it makes sense to first grab a handle, but initialize the + underlying resource at a later time. For instance when loading data + asynchronously from a slow data source, you may know what buffers and + textures are needed at an early stage of the loading process, but actually + loading the buffer or texture content can only be completed at a later time. + + For such situations, sokol-gfx resource objects can be created in two steps. + You can allocate a handle upfront with one of the 'alloc functions': + + sg_buffer sg_alloc_buffer(void) + sg_image sg_alloc_image(void) + sg_sampler sg_alloc_sampler(void) + sg_shader sg_alloc_shader(void) + sg_pipeline sg_alloc_pipeline(void) + sg_attachments sg_alloc_attachments(void) + + This will return a handle with the underlying resource object in the + ALLOC state: + + sg_image img = sg_alloc_image(); + if (sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC) { + // allocating an image handle has succeeded, otherwise + // the image pool is full + } + + Such an 'incomplete' handle can be used in most sokol-gfx rendering functions + without doing any harm, sokol-gfx will simply skip any rendering operation + that involve resources which are not in VALID state. + + At a later time (for instance once the texture has completed loading + asynchronously), the resource creation can be completed by calling one of + the 'init functions', those functions take an existing resource handle and + 'desc struct': + + void sg_init_buffer(sg_buffer buf, const sg_buffer_desc* desc) + void sg_init_image(sg_image img, const sg_image_desc* desc) + void sg_init_sampler(sg_sampler smp, const sg_sampler_desc* desc) + void sg_init_shader(sg_shader shd, const sg_shader_desc* desc) + void sg_init_pipeline(sg_pipeline pip, const sg_pipeline_desc* desc) + void sg_init_attachments(sg_attachments atts, const sg_attachments_desc* desc) + + The init functions expect a resource in ALLOC state, and after the function + returns, the resource will be either in VALID or FAILED state. Calling + an 'alloc function' followed by the matching 'init function' is fully + equivalent with calling the 'make function' alone. + + Destruction can also happen as a two-step process. The 'uninit functions' + will put a resource object from the VALID or FAILED state back into the + ALLOC state: + + void sg_uninit_buffer(sg_buffer buf) + void sg_uninit_image(sg_image img) + void sg_uninit_sampler(sg_sampler smp) + void sg_uninit_shader(sg_shader shd) + void sg_uninit_pipeline(sg_pipeline pip) + void sg_uninit_attachments(sg_attachments pass) + + Calling the 'uninit functions' with a resource that is not in the VALID or + FAILED state is a no-op. + + To finally free the pool slot for recycling call the 'dealloc functions': + + void sg_dealloc_buffer(sg_buffer buf) + void sg_dealloc_image(sg_image img) + void sg_dealloc_sampler(sg_sampler smp) + void sg_dealloc_shader(sg_shader shd) + void sg_dealloc_pipeline(sg_pipeline pip) + void sg_dealloc_attachments(sg_attachments atts) + + Calling the 'dealloc functions' on a resource that's not in ALLOC state is + a no-op, but will generate a warning log message. + + Calling an 'uninit function' and 'dealloc function' in sequence is equivalent + with calling the associated 'destroy function': + + void sg_destroy_buffer(sg_buffer buf) + void sg_destroy_image(sg_image img) + void sg_destroy_sampler(sg_sampler smp) + void sg_destroy_shader(sg_shader shd) + void sg_destroy_pipeline(sg_pipeline pip) + void sg_destroy_attachments(sg_attachments atts) + + The 'destroy functions' can be called on resources in any state and generally + do the right thing (for instance if the resource is in ALLOC state, the destroy + function will be equivalent to the 'dealloc function' and skip the 'uninit part'). + + And finally to close the circle, the 'fail functions' can be called to manually + put a resource in ALLOC state into the FAILED state: + + sg_fail_buffer(sg_buffer buf) + sg_fail_image(sg_image img) + sg_fail_sampler(sg_sampler smp) + sg_fail_shader(sg_shader shd) + sg_fail_pipeline(sg_pipeline pip) + sg_fail_attachments(sg_attachments atts) + + This is recommended if anything went wrong outside of sokol-gfx during asynchronous + resource setup (for instance a file loading operation failed). In this case, + the 'fail function' should be called instead of the 'init function'. + + Calling a 'fail function' on a resource that's not in ALLOC state is a no-op, + but will generate a warning log message. + + NOTE: that two-step resource creation usually only makes sense for buffers + and images, but not for samplers, shaders, pipelines or attachments. Most notably, trying + to create a pipeline object with a shader that's not in VALID state will + trigger a validation layer error, or if the validation layer is disabled, + result in a pipeline object in FAILED state. Same when trying to create + an attachments object with invalid image objects. + + + WEBGPU CAVEATS + ============== + For a general overview and design notes of the WebGPU backend see: + + https://floooh.github.io/2023/10/16/sokol-webgpu.html + + In general, don't expect an automatic speedup when switching from the WebGL2 + backend to the WebGPU backend. Some WebGPU functions currently actually + have a higher CPU overhead than similar WebGL2 functions, leading to the + paradoxical situation that some WebGPU code may be slower than similar WebGL2 + code. + + - when writing WGSL shader code by hand, a specific bind-slot convention + must be used: + + All uniform block structs must use `@group(0)` and bindings in the + range 0..15 + + @group(0) @binding(0..15) + + All textures, samplers and storage buffers must use `@group(1)` and + bindings must be in the range 0..127: + + @group(1) @binding(0..127) + + Note that the number of texture, sampler and storage buffer bindings + is still limited despite the large bind range: + + - up to 16 textures and sampler across all shader stages + - up to 8 storage buffers across all shader stages + + If you use sokol-shdc to generate WGSL shader code, you don't need to worry + about the above binding conventions since sokol-shdc. + + - The sokol-gfx WebGPU backend uses the sg_desc.uniform_buffer_size item + to allocate a single per-frame uniform buffer which must be big enough + to hold all data written by sg_apply_uniforms() during a single frame, + including a worst-case 256-byte alignment (e.g. each sg_apply_uniform + call will cost at least 256 bytes of uniform buffer size). The default size + is 4 MB, which is enough for 16384 sg_apply_uniform() calls per + frame (assuming the uniform data 'payload' is less than 256 bytes + per call). These rules are the same as for the Metal backend, so if + you are already using the Metal backend you'll be fine. + + - sg_apply_bindings(): the sokol-gfx WebGPU backend implements a bindgroup + cache to prevent excessive creation and destruction of BindGroup objects + when calling sg_apply_bindings(). The number of slots in the bindgroups + cache is defined in sg_desc.wgpu_bindgroups_cache_size when calling + sg_setup. The cache size must be a power-of-2 number, with the default being + 1024. The bindgroups cache behaviour can be observed by calling the new + function sg_query_frame_stats(), where the following struct items are + of interest: + + .wgpu.num_bindgroup_cache_hits + .wgpu.num_bindgroup_cache_misses + .wgpu.num_bindgroup_cache_collisions + .wgpu_num_bindgroup_cache_invalidates + .wgpu.num_bindgroup_cache_vs_hash_key_mismatch + + The value to pay attention to is `.wgpu.num_bindgroup_cache_collisions`, + if this number is consistently higher than a few percent of the + .wgpu.num_set_bindgroup value, it might be a good idea to bump the + bindgroups cache size to the next power-of-2. + + - sg_apply_viewport(): WebGPU currently has a unique restriction that viewport + rectangles must be contained entirely within the framebuffer. As a shitty + workaround sokol_gfx.h will clip incoming viewport rectangles against + the framebuffer, but this will distort the clipspace-to-screenspace mapping. + There's no proper way to handle this inside sokol_gfx.h, this must be fixed + in a future WebGPU update (see: https://github.com/gpuweb/gpuweb/issues/373 + and https://github.com/gpuweb/gpuweb/pull/5025) + + - The sokol shader compiler generally adds `diagnostic(off, derivative_uniformity);` + into the WGSL output. Currently only the Chrome WebGPU implementation seems + to recognize this. + + - Likewise, the following sokol-gfx pixel formats are not supported in WebGPU: + R16, R16SN, RG16, RG16SN, RGBA16, RGBA16SN. + Unlike unsupported vertex formats, unsupported pixel formats can be queried + in cross-backend code via sg_query_pixel_format() though. + + - The Emscripten WebGPU shim currently doesn't support the Closure minification + post-link-step (e.g. currently the emcc argument '--closure 1' or '--closure 2' + will generate broken Javascript code. + + - sokol-gfx requires the WebGPU device feature `depth32float-stencil8` to be enabled + (this should be widely supported) + + - sokol-gfx expects that the WebGPU device feature `float32-filterable` to *not* be + enabled (since this would exclude all iOS devices) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; + #if DEBUG { sokol_gfx_clib :: #library "sokol_gfx_windows_x64_gl_debug"; } + else { sokol_gfx_clib :: #library "sokol_gfx_windows_x64_gl_release"; } + } else { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; #system_library,link_always "d3d11"; + #if DEBUG { sokol_gfx_clib :: #library "sokol_gfx_windows_x64_d3d11_debug"; } + else { sokol_gfx_clib :: #library "sokol_gfx_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_windows_x64_gl_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_windows_x64_gl_release"; } + } else { + #system_library,link_always "gdi32"; #system_library,link_always "dxguid"; #system_library,link_always "user32"; #system_library,link_always "shell32"; #system_library,link_always "d3d11"; + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_windows_x64_d3d11_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_gfx_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; #system_library,link_always "OpenGL"; + #if CPU == .ARM64 { + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_arm64_gl_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_x64_gl_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_x64_gl_release"; } + } + } else { + #library,link_always "../../libclang_rt.osx"; #system_library,link_always "Cocoa"; #system_library,link_always "QuartzCore"; #system_library,link_always "Metal"; #system_library,link_always "MetalKit"; + #if CPU == .ARM64 { + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_arm64_metal_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_x64_metal_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #system_library,link_always "libXcursor"; #system_library,link_always "libX11"; #system_library,link_always "libXi"; #system_library,link_always "libGL"; + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_linux_x64_gl_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_gfx_clib :: #library,no_dll "sokol_gfx_wasm_gl_debug"; } + else { sokol_gfx_clib :: #library,no_dll "sokol_gfx_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// setup and misc functions +sg_setup :: (desc: *sg_desc) -> void #foreign sokol_gfx_clib; +sg_shutdown :: () -> void #foreign sokol_gfx_clib; +sg_isvalid :: () -> bool #foreign sokol_gfx_clib; +sg_reset_state_cache :: () -> void #foreign sokol_gfx_clib; +sg_install_trace_hooks :: (trace_hooks: *sg_trace_hooks) -> sg_trace_hooks #foreign sokol_gfx_clib; +sg_push_debug_group :: (name: *u8) -> void #foreign sokol_gfx_clib; +sg_pop_debug_group :: () -> void #foreign sokol_gfx_clib; +sg_add_commit_listener :: (listener: sg_commit_listener) -> bool #foreign sokol_gfx_clib; +sg_remove_commit_listener :: (listener: sg_commit_listener) -> bool #foreign sokol_gfx_clib; +// resource creation, destruction and updating +sg_make_buffer :: (desc: *sg_buffer_desc) -> sg_buffer #foreign sokol_gfx_clib; +sg_make_image :: (desc: *sg_image_desc) -> sg_image #foreign sokol_gfx_clib; +sg_make_sampler :: (desc: *sg_sampler_desc) -> sg_sampler #foreign sokol_gfx_clib; +sg_make_shader :: (desc: *sg_shader_desc) -> sg_shader #foreign sokol_gfx_clib; +sg_make_pipeline :: (desc: *sg_pipeline_desc) -> sg_pipeline #foreign sokol_gfx_clib; +sg_make_attachments :: (desc: *sg_attachments_desc) -> sg_attachments #foreign sokol_gfx_clib; +sg_destroy_buffer :: (buf: sg_buffer) -> void #foreign sokol_gfx_clib; +sg_destroy_image :: (img: sg_image) -> void #foreign sokol_gfx_clib; +sg_destroy_sampler :: (smp: sg_sampler) -> void #foreign sokol_gfx_clib; +sg_destroy_shader :: (shd: sg_shader) -> void #foreign sokol_gfx_clib; +sg_destroy_pipeline :: (pip: sg_pipeline) -> void #foreign sokol_gfx_clib; +sg_destroy_attachments :: (atts: sg_attachments) -> void #foreign sokol_gfx_clib; +sg_update_buffer :: (buf: sg_buffer, data: *sg_range) -> void #foreign sokol_gfx_clib; +sg_update_image :: (img: sg_image, data: *sg_image_data) -> void #foreign sokol_gfx_clib; +sg_append_buffer :: (buf: sg_buffer, data: *sg_range) -> s32 #foreign sokol_gfx_clib; +sg_query_buffer_overflow :: (buf: sg_buffer) -> bool #foreign sokol_gfx_clib; +sg_query_buffer_will_overflow :: (buf: sg_buffer, size: u64) -> bool #foreign sokol_gfx_clib; +// render and compute functions +sg_begin_pass :: (pass: *sg_pass) -> void #foreign sokol_gfx_clib; +sg_apply_viewport :: (x: s32, y: s32, width: s32, height: s32, origin_top_left: bool) -> void #foreign sokol_gfx_clib; +sg_apply_viewportf :: (x: float, y: float, width: float, height: float, origin_top_left: bool) -> void #foreign sokol_gfx_clib; +sg_apply_scissor_rect :: (x: s32, y: s32, width: s32, height: s32, origin_top_left: bool) -> void #foreign sokol_gfx_clib; +sg_apply_scissor_rectf :: (x: float, y: float, width: float, height: float, origin_top_left: bool) -> void #foreign sokol_gfx_clib; +sg_apply_pipeline :: (pip: sg_pipeline) -> void #foreign sokol_gfx_clib; +sg_apply_bindings :: (bindings: *sg_bindings) -> void #foreign sokol_gfx_clib; +sg_apply_uniforms :: (ub_slot: s32, data: *sg_range) -> void #foreign sokol_gfx_clib; +sg_draw :: (base_element: s32, num_elements: s32, num_instances: s32) -> void #foreign sokol_gfx_clib; +sg_dispatch :: (num_groups_x: s32, num_groups_y: s32, num_groups_z: s32) -> void #foreign sokol_gfx_clib; +sg_end_pass :: () -> void #foreign sokol_gfx_clib; +sg_commit :: () -> void #foreign sokol_gfx_clib; +// getting information +sg_query_desc :: () -> sg_desc #foreign sokol_gfx_clib; +sg_query_backend :: () -> sg_backend #foreign sokol_gfx_clib; +sg_query_features :: () -> sg_features #foreign sokol_gfx_clib; +sg_query_limits :: () -> sg_limits #foreign sokol_gfx_clib; +sg_query_pixelformat :: (fmt: sg_pixel_format) -> sg_pixelformat_info #foreign sokol_gfx_clib; +sg_query_row_pitch :: (fmt: sg_pixel_format, width: s32, row_align_bytes: s32) -> s32 #foreign sokol_gfx_clib; +sg_query_surface_pitch :: (fmt: sg_pixel_format, width: s32, height: s32, row_align_bytes: s32) -> s32 #foreign sokol_gfx_clib; +// get current state of a resource (INITIAL, ALLOC, VALID, FAILED, INVALID) +sg_query_buffer_state :: (buf: sg_buffer) -> sg_resource_state #foreign sokol_gfx_clib; +sg_query_image_state :: (img: sg_image) -> sg_resource_state #foreign sokol_gfx_clib; +sg_query_sampler_state :: (smp: sg_sampler) -> sg_resource_state #foreign sokol_gfx_clib; +sg_query_shader_state :: (shd: sg_shader) -> sg_resource_state #foreign sokol_gfx_clib; +sg_query_pipeline_state :: (pip: sg_pipeline) -> sg_resource_state #foreign sokol_gfx_clib; +sg_query_attachments_state :: (atts: sg_attachments) -> sg_resource_state #foreign sokol_gfx_clib; +// get runtime information about a resource +sg_query_buffer_info :: (buf: sg_buffer) -> sg_buffer_info #foreign sokol_gfx_clib; +sg_query_image_info :: (img: sg_image) -> sg_image_info #foreign sokol_gfx_clib; +sg_query_sampler_info :: (smp: sg_sampler) -> sg_sampler_info #foreign sokol_gfx_clib; +sg_query_shader_info :: (shd: sg_shader) -> sg_shader_info #foreign sokol_gfx_clib; +sg_query_pipeline_info :: (pip: sg_pipeline) -> sg_pipeline_info #foreign sokol_gfx_clib; +sg_query_attachments_info :: (atts: sg_attachments) -> sg_attachments_info #foreign sokol_gfx_clib; +// get desc structs matching a specific resource (NOTE that not all creation attributes may be provided) +sg_query_buffer_desc :: (buf: sg_buffer) -> sg_buffer_desc #foreign sokol_gfx_clib; +sg_query_image_desc :: (img: sg_image) -> sg_image_desc #foreign sokol_gfx_clib; +sg_query_sampler_desc :: (smp: sg_sampler) -> sg_sampler_desc #foreign sokol_gfx_clib; +sg_query_shader_desc :: (shd: sg_shader) -> sg_shader_desc #foreign sokol_gfx_clib; +sg_query_pipeline_desc :: (pip: sg_pipeline) -> sg_pipeline_desc #foreign sokol_gfx_clib; +sg_query_attachments_desc :: (atts: sg_attachments) -> sg_attachments_desc #foreign sokol_gfx_clib; +// get resource creation desc struct with their default values replaced +sg_query_buffer_defaults :: (desc: *sg_buffer_desc) -> sg_buffer_desc #foreign sokol_gfx_clib; +sg_query_image_defaults :: (desc: *sg_image_desc) -> sg_image_desc #foreign sokol_gfx_clib; +sg_query_sampler_defaults :: (desc: *sg_sampler_desc) -> sg_sampler_desc #foreign sokol_gfx_clib; +sg_query_shader_defaults :: (desc: *sg_shader_desc) -> sg_shader_desc #foreign sokol_gfx_clib; +sg_query_pipeline_defaults :: (desc: *sg_pipeline_desc) -> sg_pipeline_desc #foreign sokol_gfx_clib; +sg_query_attachments_defaults :: (desc: *sg_attachments_desc) -> sg_attachments_desc #foreign sokol_gfx_clib; +// assorted query functions +sg_query_buffer_size :: (buf: sg_buffer) -> u64 #foreign sokol_gfx_clib; +sg_query_buffer_type :: (buf: sg_buffer) -> sg_buffer_type #foreign sokol_gfx_clib; +sg_query_buffer_usage :: (buf: sg_buffer) -> sg_usage #foreign sokol_gfx_clib; +sg_query_image_type :: (img: sg_image) -> sg_image_type #foreign sokol_gfx_clib; +sg_query_image_width :: (img: sg_image) -> s32 #foreign sokol_gfx_clib; +sg_query_image_height :: (img: sg_image) -> s32 #foreign sokol_gfx_clib; +sg_query_image_num_slices :: (img: sg_image) -> s32 #foreign sokol_gfx_clib; +sg_query_image_num_mipmaps :: (img: sg_image) -> s32 #foreign sokol_gfx_clib; +sg_query_image_pixelformat :: (img: sg_image) -> sg_pixel_format #foreign sokol_gfx_clib; +sg_query_image_usage :: (img: sg_image) -> sg_usage #foreign sokol_gfx_clib; +sg_query_image_sample_count :: (img: sg_image) -> s32 #foreign sokol_gfx_clib; +// separate resource allocation and initialization (for async setup) +sg_alloc_buffer :: () -> sg_buffer #foreign sokol_gfx_clib; +sg_alloc_image :: () -> sg_image #foreign sokol_gfx_clib; +sg_alloc_sampler :: () -> sg_sampler #foreign sokol_gfx_clib; +sg_alloc_shader :: () -> sg_shader #foreign sokol_gfx_clib; +sg_alloc_pipeline :: () -> sg_pipeline #foreign sokol_gfx_clib; +sg_alloc_attachments :: () -> sg_attachments #foreign sokol_gfx_clib; +sg_dealloc_buffer :: (buf: sg_buffer) -> void #foreign sokol_gfx_clib; +sg_dealloc_image :: (img: sg_image) -> void #foreign sokol_gfx_clib; +sg_dealloc_sampler :: (smp: sg_sampler) -> void #foreign sokol_gfx_clib; +sg_dealloc_shader :: (shd: sg_shader) -> void #foreign sokol_gfx_clib; +sg_dealloc_pipeline :: (pip: sg_pipeline) -> void #foreign sokol_gfx_clib; +sg_dealloc_attachments :: (attachments: sg_attachments) -> void #foreign sokol_gfx_clib; +sg_init_buffer :: (buf: sg_buffer, desc: *sg_buffer_desc) -> void #foreign sokol_gfx_clib; +sg_init_image :: (img: sg_image, desc: *sg_image_desc) -> void #foreign sokol_gfx_clib; +sg_init_sampler :: (smg: sg_sampler, desc: *sg_sampler_desc) -> void #foreign sokol_gfx_clib; +sg_init_shader :: (shd: sg_shader, desc: *sg_shader_desc) -> void #foreign sokol_gfx_clib; +sg_init_pipeline :: (pip: sg_pipeline, desc: *sg_pipeline_desc) -> void #foreign sokol_gfx_clib; +sg_init_attachments :: (attachments: sg_attachments, desc: *sg_attachments_desc) -> void #foreign sokol_gfx_clib; +sg_uninit_buffer :: (buf: sg_buffer) -> void #foreign sokol_gfx_clib; +sg_uninit_image :: (img: sg_image) -> void #foreign sokol_gfx_clib; +sg_uninit_sampler :: (smp: sg_sampler) -> void #foreign sokol_gfx_clib; +sg_uninit_shader :: (shd: sg_shader) -> void #foreign sokol_gfx_clib; +sg_uninit_pipeline :: (pip: sg_pipeline) -> void #foreign sokol_gfx_clib; +sg_uninit_attachments :: (atts: sg_attachments) -> void #foreign sokol_gfx_clib; +sg_fail_buffer :: (buf: sg_buffer) -> void #foreign sokol_gfx_clib; +sg_fail_image :: (img: sg_image) -> void #foreign sokol_gfx_clib; +sg_fail_sampler :: (smp: sg_sampler) -> void #foreign sokol_gfx_clib; +sg_fail_shader :: (shd: sg_shader) -> void #foreign sokol_gfx_clib; +sg_fail_pipeline :: (pip: sg_pipeline) -> void #foreign sokol_gfx_clib; +sg_fail_attachments :: (atts: sg_attachments) -> void #foreign sokol_gfx_clib; +// frame stats +sg_enable_frame_stats :: () -> void #foreign sokol_gfx_clib; +sg_disable_frame_stats :: () -> void #foreign sokol_gfx_clib; +sg_frame_stats_enabled :: () -> bool #foreign sokol_gfx_clib; +sg_query_frame_stats :: () -> sg_frame_stats #foreign sokol_gfx_clib; +// D3D11: return ID3D11Device +sg_d3d11_device :: () -> *void #foreign sokol_gfx_clib; +// D3D11: return ID3D11DeviceContext +sg_d3d11_device_context :: () -> *void #foreign sokol_gfx_clib; +// D3D11: get internal buffer resource objects +sg_d3d11_query_buffer_info :: (buf: sg_buffer) -> sg_d3d11_buffer_info #foreign sokol_gfx_clib; +// D3D11: get internal image resource objects +sg_d3d11_query_image_info :: (img: sg_image) -> sg_d3d11_image_info #foreign sokol_gfx_clib; +// D3D11: get internal sampler resource objects +sg_d3d11_query_sampler_info :: (smp: sg_sampler) -> sg_d3d11_sampler_info #foreign sokol_gfx_clib; +// D3D11: get internal shader resource objects +sg_d3d11_query_shader_info :: (shd: sg_shader) -> sg_d3d11_shader_info #foreign sokol_gfx_clib; +// D3D11: get internal pipeline resource objects +sg_d3d11_query_pipeline_info :: (pip: sg_pipeline) -> sg_d3d11_pipeline_info #foreign sokol_gfx_clib; +// D3D11: get internal pass resource objects +sg_d3d11_query_attachments_info :: (atts: sg_attachments) -> sg_d3d11_attachments_info #foreign sokol_gfx_clib; +// Metal: return __bridge-casted MTLDevice +sg_mtl_device :: () -> *void #foreign sokol_gfx_clib; +// Metal: return __bridge-casted MTLRenderCommandEncoder when inside render pass (otherwise zero) +sg_mtl_render_command_encoder :: () -> *void #foreign sokol_gfx_clib; +// Metal: return __bridge-casted MTLComputeCommandEncoder when inside compute pass (otherwise zero) +sg_mtl_compute_command_encoder :: () -> *void #foreign sokol_gfx_clib; +// Metal: get internal __bridge-casted buffer resource objects +sg_mtl_query_buffer_info :: (buf: sg_buffer) -> sg_mtl_buffer_info #foreign sokol_gfx_clib; +// Metal: get internal __bridge-casted image resource objects +sg_mtl_query_image_info :: (img: sg_image) -> sg_mtl_image_info #foreign sokol_gfx_clib; +// Metal: get internal __bridge-casted sampler resource objects +sg_mtl_query_sampler_info :: (smp: sg_sampler) -> sg_mtl_sampler_info #foreign sokol_gfx_clib; +// Metal: get internal __bridge-casted shader resource objects +sg_mtl_query_shader_info :: (shd: sg_shader) -> sg_mtl_shader_info #foreign sokol_gfx_clib; +// Metal: get internal __bridge-casted pipeline resource objects +sg_mtl_query_pipeline_info :: (pip: sg_pipeline) -> sg_mtl_pipeline_info #foreign sokol_gfx_clib; +// WebGPU: return WGPUDevice object +sg_wgpu_device :: () -> *void #foreign sokol_gfx_clib; +// WebGPU: return WGPUQueue object +sg_wgpu_queue :: () -> *void #foreign sokol_gfx_clib; +// WebGPU: return this frame's WGPUCommandEncoder +sg_wgpu_command_encoder :: () -> *void #foreign sokol_gfx_clib; +// WebGPU: return WGPURenderPassEncoder of current pass (returns 0 when outside pass or in a compute pass) +sg_wgpu_render_pass_encoder :: () -> *void #foreign sokol_gfx_clib; +// WebGPU: return WGPUComputePassEncoder of current pass (returns 0 when outside pass or in a render pass) +sg_wgpu_compute_pass_encoder :: () -> *void #foreign sokol_gfx_clib; +// WebGPU: get internal buffer resource objects +sg_wgpu_query_buffer_info :: (buf: sg_buffer) -> sg_wgpu_buffer_info #foreign sokol_gfx_clib; +// WebGPU: get internal image resource objects +sg_wgpu_query_image_info :: (img: sg_image) -> sg_wgpu_image_info #foreign sokol_gfx_clib; +// WebGPU: get internal sampler resource objects +sg_wgpu_query_sampler_info :: (smp: sg_sampler) -> sg_wgpu_sampler_info #foreign sokol_gfx_clib; +// WebGPU: get internal shader resource objects +sg_wgpu_query_shader_info :: (shd: sg_shader) -> sg_wgpu_shader_info #foreign sokol_gfx_clib; +// WebGPU: get internal pipeline resource objects +sg_wgpu_query_pipeline_info :: (pip: sg_pipeline) -> sg_wgpu_pipeline_info #foreign sokol_gfx_clib; +// WebGPU: get internal pass resource objects +sg_wgpu_query_attachments_info :: (atts: sg_attachments) -> sg_wgpu_attachments_info #foreign sokol_gfx_clib; +// GL: get internal buffer resource objects +sg_gl_query_buffer_info :: (buf: sg_buffer) -> sg_gl_buffer_info #foreign sokol_gfx_clib; +// GL: get internal image resource objects +sg_gl_query_image_info :: (img: sg_image) -> sg_gl_image_info #foreign sokol_gfx_clib; +// GL: get internal sampler resource objects +sg_gl_query_sampler_info :: (smp: sg_sampler) -> sg_gl_sampler_info #foreign sokol_gfx_clib; +// GL: get internal shader resource objects +sg_gl_query_shader_info :: (shd: sg_shader) -> sg_gl_shader_info #foreign sokol_gfx_clib; +// GL: get internal pass resource objects +sg_gl_query_attachments_info :: (atts: sg_attachments) -> sg_gl_attachments_info #foreign sokol_gfx_clib; + +sg_buffer :: struct { + id : u32; +} + +sg_image :: struct { + id : u32; +} + +sg_sampler :: struct { + id : u32; +} + +sg_shader :: struct { + id : u32; +} + +sg_pipeline :: struct { + id : u32; +} + +sg_attachments :: struct { + id : u32; +} + +sg_range :: struct { + ptr : *void; + size : u64; +} + +INVALID_ID :: 0; +NUM_INFLIGHT_FRAMES :: 2; +MAX_COLOR_ATTACHMENTS :: 4; +MAX_UNIFORMBLOCK_MEMBERS :: 16; +MAX_VERTEX_ATTRIBUTES :: 16; +MAX_MIPMAPS :: 16; +MAX_TEXTUREARRAY_LAYERS :: 128; +MAX_UNIFORMBLOCK_BINDSLOTS :: 8; +MAX_VERTEXBUFFER_BINDSLOTS :: 8; +MAX_IMAGE_BINDSLOTS :: 16; +MAX_SAMPLER_BINDSLOTS :: 16; +MAX_STORAGEBUFFER_BINDSLOTS :: 8; +MAX_IMAGE_SAMPLER_PAIRS :: 16; + +sg_color :: struct { + r : float; + g : float; + b : float; + a : float; +} + +/* + sg_backend + + The active 3D-API backend, use the function sg_query_backend() + to get the currently active backend. +*/ +sg_backend :: enum u32 { + GLCORE; + GLES3; + D3D11; + METAL_IOS; + METAL_MACOS; + METAL_SIMULATOR; + WGPU; + DUMMY; +} + +/* + sg_pixel_format + + sokol_gfx.h basically uses the same pixel formats as WebGPU, since these + are supported on most newer GPUs. + + A pixelformat name consist of three parts: + + - components (R, RG, RGB or RGBA) + - bit width per component (8, 16 or 32) + - component data type: + - unsigned normalized (no postfix) + - signed normalized (SN postfix) + - unsigned integer (UI postfix) + - signed integer (SI postfix) + - float (F postfix) + + Not all pixel formats can be used for everything, call sg_query_pixelformat() + to inspect the capabilities of a given pixelformat. The function returns + an sg_pixelformat_info struct with the following members: + + - sample: the pixelformat can be sampled as texture at least with + nearest filtering + - filter: the pixelformat can be sampled as texture with linear + filtering + - render: the pixelformat can be used as render-pass attachment + - blend: blending is supported when used as render-pass attachment + - msaa: multisample-antialiasing is supported when used + as render-pass attachment + - depth: the pixelformat can be used for depth-stencil attachments + - compressed: this is a block-compressed format + - bytes_per_pixel: the numbers of bytes in a pixel (0 for compressed formats) + + The default pixel format for texture images is SG_PIXELFORMAT_RGBA8. + + The default pixel format for render target images is platform-dependent + and taken from the sg_environment struct passed into sg_setup(). Typically + the default formats are: + + - for the Metal, D3D11 and WebGPU backends: SG_PIXELFORMAT_BGRA8 + - for GL backends: SG_PIXELFORMAT_RGBA8 +*/ +sg_pixel_format :: enum u32 { + DEFAULT; + NONE; + R8; + R8SN; + R8UI; + R8SI; + R16; + R16SN; + R16UI; + R16SI; + R16F; + RG8; + RG8SN; + RG8UI; + RG8SI; + R32UI; + R32SI; + R32F; + RG16; + RG16SN; + RG16UI; + RG16SI; + RG16F; + RGBA8; + SRGB8A8; + RGBA8SN; + RGBA8UI; + RGBA8SI; + BGRA8; + RGB10A2; + RG11B10F; + RGB9E5; + RG32UI; + RG32SI; + RG32F; + RGBA16; + RGBA16SN; + RGBA16UI; + RGBA16SI; + RGBA16F; + RGBA32UI; + RGBA32SI; + RGBA32F; + DEPTH; + DEPTH_STENCIL; + BC1_RGBA; + BC2_RGBA; + BC3_RGBA; + BC3_SRGBA; + BC4_R; + BC4_RSN; + BC5_RG; + BC5_RGSN; + BC6H_RGBF; + BC6H_RGBUF; + BC7_RGBA; + BC7_SRGBA; + ETC2_RGB8; + ETC2_SRGB8; + ETC2_RGB8A1; + ETC2_RGBA8; + ETC2_SRGB8A8; + EAC_R11; + EAC_R11SN; + EAC_RG11; + EAC_RG11SN; + ASTC_4x4_RGBA; + ASTC_4x4_SRGBA; +} + +sg_pixelformat_info :: struct { + sample : bool; + filter : bool; + render : bool; + blend : bool; + msaa : bool; + depth : bool; + compressed : bool; + bytes_per_pixel : s32; +} + +sg_features :: struct { + origin_top_left : bool; + image_clamp_to_border : bool; + mrt_independent_blend_state : bool; + mrt_independent_write_mask : bool; + compute : bool; + msaa_image_bindings : bool; +} + +sg_limits :: struct { + max_image_size_2d : s32; + max_image_size_cube : s32; + max_image_size_3d : s32; + max_image_size_array : s32; + max_image_array_layers : s32; + max_vertex_attrs : s32; + gl_max_vertex_uniform_components : s32; + gl_max_combined_texture_image_units : s32; +} + +/* + sg_resource_state + + The current state of a resource in its resource pool. + Resources start in the INITIAL state, which means the + pool slot is unoccupied and can be allocated. When a resource is + created, first an id is allocated, and the resource pool slot + is set to state ALLOC. After allocation, the resource is + initialized, which may result in the VALID or FAILED state. The + reason why allocation and initialization are separate is because + some resource types (e.g. buffers and images) might be asynchronously + initialized by the user application. If a resource which is not + in the VALID state is attempted to be used for rendering, rendering + operations will silently be dropped. + + The special INVALID state is returned in sg_query_xxx_state() if no + resource object exists for the provided resource id. +*/ +sg_resource_state :: enum u32 { + INITIAL; + ALLOC; + VALID; + FAILED; + INVALID; +} + +/* + sg_usage + + A resource usage hint describing the update strategy of + buffers and images. This is used in the sg_buffer_desc.usage + and sg_image_desc.usage members when creating buffers + and images: + + SG_USAGE_IMMUTABLE: the resource will never be updated with + new (CPU-side) data, instead the content of the + resource must be provided on creation + SG_USAGE_DYNAMIC: the resource will be updated infrequently + with new data (this could range from "once + after creation", to "quite often but not + every frame") + SG_USAGE_STREAM: the resource will be updated each frame + with new content + + The rendering backends use this hint to prevent that the + CPU needs to wait for the GPU when attempting to update + a resource that might be currently accessed by the GPU. + + Resource content is updated with the functions sg_update_buffer() or + sg_append_buffer() for buffer objects, and sg_update_image() for image + objects. For the sg_update_*() functions, only one update is allowed per + frame and resource object, while sg_append_buffer() can be called + multiple times per frame on the same buffer. The application must update + all data required for rendering (this means that the update data can be + smaller than the resource size, if only a part of the overall resource + size is used for rendering, you only need to make sure that the data that + *is* used is valid). + + The default usage is SG_USAGE_IMMUTABLE. +*/ +sg_usage :: enum u32 { + DEFAULT; + IMMUTABLE; + DYNAMIC; + STREAM; +} + +/* + sg_buffer_type + + Indicates whether a buffer will be bound as vertex-, + index- or storage-buffer. + + Used in the sg_buffer_desc.type member when creating a buffer. + + The default value is SG_BUFFERTYPE_VERTEXBUFFER. +*/ +sg_buffer_type :: enum u32 { + DEFAULT; + VERTEXBUFFER; + INDEXBUFFER; + STORAGEBUFFER; +} + +/* + sg_index_type + + Indicates whether indexed rendering (fetching vertex-indices from an + index buffer) is used, and if yes, the index data type (16- or 32-bits). + + This is used in the sg_pipeline_desc.index_type member when creating a + pipeline object. + + The default index type is SG_INDEXTYPE_NONE. +*/ +sg_index_type :: enum u32 { + DEFAULT; + NONE; + UINT16; + UINT32; +} + +/* + sg_image_type + + Indicates the basic type of an image object (2D-texture, cubemap, + 3D-texture or 2D-array-texture). Used in the sg_image_desc.type member when + creating an image, and in sg_shader_image_desc to describe a sampled texture + in the shader (both must match and will be checked in the validation layer + when calling sg_apply_bindings). + + The default image type when creating an image is SG_IMAGETYPE_2D. +*/ +sg_image_type :: enum u32 { + DEFAULT; + _2D; + CUBE; + _3D; + ARRAY; +} + +/* + sg_image_sample_type + + The basic data type of a texture sample as expected by a shader. + Must be provided in sg_shader_image and used by the validation + layer in sg_apply_bindings() to check if the provided image object + is compatible with what the shader expects. Apart from the sokol-gfx + validation layer, WebGPU is the only backend API which actually requires + matching texture and sampler type to be provided upfront for validation + (other 3D APIs treat texture/sampler type mismatches as undefined behaviour). + + NOTE that the following texture pixel formats require the use + of SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT, combined with a sampler + of type SG_SAMPLERTYPE_NONFILTERING: + + - SG_PIXELFORMAT_R32F + - SG_PIXELFORMAT_RG32F + - SG_PIXELFORMAT_RGBA32F + + (when using sokol-shdc, also check out the meta tags `@image_sample_type` + and `@sampler_type`) +*/ +sg_image_sample_type :: enum u32 { + DEFAULT; + FLOAT; + DEPTH; + SINT; + UINT; + UNFILTERABLE_FLOAT; +} + +/* + sg_sampler_type + + The basic type of a texture sampler (sampling vs comparison) as + defined in a shader. Must be provided in sg_shader_sampler_desc. + + sg_image_sample_type and sg_sampler_type for a texture/sampler + pair must be compatible with each other, specifically only + the following pairs are allowed: + + - SG_IMAGESAMPLETYPE_FLOAT => (SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING) + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_SINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_UINT => SG_SAMPLERTYPE_NONFILTERING + - SG_IMAGESAMPLETYPE_DEPTH => SG_SAMPLERTYPE_COMPARISON +*/ +sg_sampler_type :: enum u32 { + DEFAULT; + FILTERING; + NONFILTERING; + COMPARISON; +} + +/* + sg_cube_face + + The cubemap faces. Use these as indices in the sg_image_desc.content + array. +*/ +sg_cube_face :: enum u32 { + POS_X; + NEG_X; + POS_Y; + NEG_Y; + POS_Z; + NEG_Z; +} + +/* + sg_primitive_type + + This is the common subset of 3D primitive types supported across all 3D + APIs. This is used in the sg_pipeline_desc.primitive_type member when + creating a pipeline object. + + The default primitive type is SG_PRIMITIVETYPE_TRIANGLES. +*/ +sg_primitive_type :: enum u32 { + DEFAULT; + POINTS; + LINES; + LINE_STRIP; + TRIANGLES; + TRIANGLE_STRIP; +} + +/* + sg_filter + + The filtering mode when sampling a texture image. This is + used in the sg_sampler_desc.min_filter, sg_sampler_desc.mag_filter + and sg_sampler_desc.mipmap_filter members when creating a sampler object. + + For the default is SG_FILTER_NEAREST. +*/ +sg_filter :: enum u32 { + DEFAULT; + NEAREST; + LINEAR; +} + +/* + sg_wrap + + The texture coordinates wrapping mode when sampling a texture + image. This is used in the sg_image_desc.wrap_u, .wrap_v + and .wrap_w members when creating an image. + + The default wrap mode is SG_WRAP_REPEAT. + + NOTE: SG_WRAP_CLAMP_TO_BORDER is not supported on all backends + and platforms. To check for support, call sg_query_features() + and check the "clamp_to_border" boolean in the returned + sg_features struct. + + Platforms which don't support SG_WRAP_CLAMP_TO_BORDER will silently fall back + to SG_WRAP_CLAMP_TO_EDGE without a validation error. +*/ +sg_wrap :: enum u32 { + DEFAULT; + REPEAT; + CLAMP_TO_EDGE; + CLAMP_TO_BORDER; + MIRRORED_REPEAT; +} + +/* + sg_border_color + + The border color to use when sampling a texture, and the UV wrap + mode is SG_WRAP_CLAMP_TO_BORDER. + + The default border color is SG_BORDERCOLOR_OPAQUE_BLACK +*/ +sg_border_color :: enum u32 { + DEFAULT; + TRANSPARENT_BLACK; + OPAQUE_BLACK; + OPAQUE_WHITE; +} + +/* + sg_vertex_format + + The data type of a vertex component. This is used to describe + the layout of input vertex data when creating a pipeline object. + + NOTE that specific mapping rules exist from the CPU-side vertex + formats to the vertex attribute base type in the vertex shader code + (see doc header section 'ON VERTEX FORMATS'). +*/ +sg_vertex_format :: enum u32 { + INVALID; + FLOAT; + FLOAT2; + FLOAT3; + FLOAT4; + INT; + INT2; + INT3; + INT4; + UINT; + UINT2; + UINT3; + UINT4; + BYTE4; + BYTE4N; + UBYTE4; + UBYTE4N; + SHORT2; + SHORT2N; + USHORT2; + USHORT2N; + SHORT4; + SHORT4N; + USHORT4; + USHORT4N; + UINT10_N2; + HALF2; + HALF4; +} + +/* + sg_vertex_step + + Defines whether the input pointer of a vertex input stream is advanced + 'per vertex' or 'per instance'. The default step-func is + SG_VERTEXSTEP_PER_VERTEX. SG_VERTEXSTEP_PER_INSTANCE is used with + instanced-rendering. + + The vertex-step is part of the vertex-layout definition + when creating pipeline objects. +*/ +sg_vertex_step :: enum u32 { + DEFAULT; + PER_VERTEX; + PER_INSTANCE; +} + +/* + sg_uniform_type + + The data type of a uniform block member. This is used to + describe the internal layout of uniform blocks when creating + a shader object. This is only required for the GL backend, all + other backends will ignore the interior layout of uniform blocks. +*/ +sg_uniform_type :: enum u32 { + INVALID; + FLOAT; + FLOAT2; + FLOAT3; + FLOAT4; + INT; + INT2; + INT3; + INT4; + MAT4; +} + +/* + sg_uniform_layout + + A hint for the interior memory layout of uniform blocks. This is + only relevant for the GL backend where the internal layout + of uniform blocks must be known to sokol-gfx. For all other backends the + internal memory layout of uniform blocks doesn't matter, sokol-gfx + will just pass uniform data as an opaque memory blob to the + 3D backend. + + SG_UNIFORMLAYOUT_NATIVE (default) + Native layout means that a 'backend-native' memory layout + is used. For the GL backend this means that uniforms + are packed tightly in memory (e.g. there are no padding + bytes). + + SG_UNIFORMLAYOUT_STD140 + The memory layout is a subset of std140. Arrays are only + allowed for the FLOAT4, INT4 and MAT4. Alignment is as + is as follows: + + FLOAT, INT: 4 byte alignment + FLOAT2, INT2: 8 byte alignment + FLOAT3, INT3: 16 byte alignment(!) + FLOAT4, INT4: 16 byte alignment + MAT4: 16 byte alignment + FLOAT4[], INT4[]: 16 byte alignment + + The overall size of the uniform block must be a multiple + of 16. + + For more information search for 'UNIFORM DATA LAYOUT' in the documentation block + at the start of the header. +*/ +sg_uniform_layout :: enum u32 { + DEFAULT; + NATIVE; + STD140; +} + +/* + sg_cull_mode + + The face-culling mode, this is used in the + sg_pipeline_desc.cull_mode member when creating a + pipeline object. + + The default cull mode is SG_CULLMODE_NONE +*/ +sg_cull_mode :: enum u32 { + DEFAULT; + NONE; + FRONT; + BACK; +} + +/* + sg_face_winding + + The vertex-winding rule that determines a front-facing primitive. This + is used in the member sg_pipeline_desc.face_winding + when creating a pipeline object. + + The default winding is SG_FACEWINDING_CW (clockwise) +*/ +sg_face_winding :: enum u32 { + DEFAULT; + CCW; + CW; +} + +/* + sg_compare_func + + The compare-function for configuring depth- and stencil-ref tests + in pipeline objects, and for texture samplers which perform a comparison + instead of regular sampling operation. + + Used in the following structs: + + sg_pipeline_desc + .depth + .compare + .stencil + .front.compare + .back.compare + + sg_sampler_desc + .compare + + The default compare func for depth- and stencil-tests is + SG_COMPAREFUNC_ALWAYS. + + The default compare func for samplers is SG_COMPAREFUNC_NEVER. +*/ +sg_compare_func :: enum u32 { + DEFAULT; + NEVER; + LESS; + EQUAL; + LESS_EQUAL; + GREATER; + NOT_EQUAL; + GREATER_EQUAL; + ALWAYS; +} + +/* + sg_stencil_op + + The operation performed on a currently stored stencil-value when a + comparison test passes or fails. This is used when creating a pipeline + object in the following sg_pipeline_desc struct items: + + sg_pipeline_desc + .stencil + .front + .fail_op + .depth_fail_op + .pass_op + .back + .fail_op + .depth_fail_op + .pass_op + + The default value is SG_STENCILOP_KEEP. +*/ +sg_stencil_op :: enum u32 { + DEFAULT; + KEEP; + ZERO; + REPLACE; + INCR_CLAMP; + DECR_CLAMP; + INVERT; + INCR_WRAP; + DECR_WRAP; +} + +/* + sg_blend_factor + + The source and destination factors in blending operations. + This is used in the following members when creating a pipeline object: + + sg_pipeline_desc + .colors[i] + .blend + .src_factor_rgb + .dst_factor_rgb + .src_factor_alpha + .dst_factor_alpha + + The default value is SG_BLENDFACTOR_ONE for source + factors, and for the destination SG_BLENDFACTOR_ZERO if the associated + blend-op is ADD, SUBTRACT or REVERSE_SUBTRACT or SG_BLENDFACTOR_ONE + if the associated blend-op is MIN or MAX. +*/ +sg_blend_factor :: enum u32 { + DEFAULT; + ZERO; + ONE; + SRC_COLOR; + ONE_MINUS_SRC_COLOR; + SRC_ALPHA; + ONE_MINUS_SRC_ALPHA; + DST_COLOR; + ONE_MINUS_DST_COLOR; + DST_ALPHA; + ONE_MINUS_DST_ALPHA; + SRC_ALPHA_SATURATED; + BLEND_COLOR; + ONE_MINUS_BLEND_COLOR; + BLEND_ALPHA; + ONE_MINUS_BLEND_ALPHA; +} + +/* + sg_blend_op + + Describes how the source and destination values are combined in the + fragment blending operation. It is used in the following struct items + when creating a pipeline object: + + sg_pipeline_desc + .colors[i] + .blend + .op_rgb + .op_alpha + + The default value is SG_BLENDOP_ADD. +*/ +sg_blend_op :: enum u32 { + DEFAULT; + ADD; + SUBTRACT; + REVERSE_SUBTRACT; + MIN; + MAX; +} + +/* + sg_color_mask + + Selects the active color channels when writing a fragment color to the + framebuffer. This is used in the members + sg_pipeline_desc.colors[i].write_mask when creating a pipeline object. + + The default colormask is SG_COLORMASK_RGBA (write all colors channels) + + NOTE: since the color mask value 0 is reserved for the default value + (SG_COLORMASK_RGBA), use SG_COLORMASK_NONE if all color channels + should be disabled. +*/ +sg_color_mask :: enum u32 { + DEFAULT :: 0; + NONE :: 16; + R :: 1; + G :: 2; + RG :: 3; + B :: 4; + RB :: 5; + GB :: 6; + RGB :: 7; + A :: 8; + RA :: 9; + GA :: 10; + RGA :: 11; + BA :: 12; + RBA :: 13; + GBA :: 14; + RGBA :: 15; +} + +/* + sg_load_action + + Defines the load action that should be performed at the start of a render pass: + + SG_LOADACTION_CLEAR: clear the render target + SG_LOADACTION_LOAD: load the previous content of the render target + SG_LOADACTION_DONTCARE: leave the render target in an undefined state + + This is used in the sg_pass_action structure. + + The default load action for all pass attachments is SG_LOADACTION_CLEAR, + with the values rgba = { 0.5f, 0.5f, 0.5f, 1.0f }, depth=1.0f and stencil=0. + + If you want to override the default behaviour, it is important to not + only set the clear color, but the 'action' field as well (as long as this + is _SG_LOADACTION_DEFAULT, the value fields will be ignored). +*/ +sg_load_action :: enum u32 { + DEFAULT; + CLEAR; + LOAD; + DONTCARE; +} + +/* + sg_store_action + + Defines the store action that should be performed at the end of a render pass: + + SG_STOREACTION_STORE: store the rendered content to the color attachment image + SG_STOREACTION_DONTCARE: allows the GPU to discard the rendered content +*/ +sg_store_action :: enum u32 { + DEFAULT; + STORE; + DONTCARE; +} + +sg_color_attachment_action :: struct { + load_action : sg_load_action; + store_action : sg_store_action; + clear_value : sg_color; +} + +sg_depth_attachment_action :: struct { + load_action : sg_load_action; + store_action : sg_store_action; + clear_value : float; +} + +sg_stencil_attachment_action :: struct { + load_action : sg_load_action; + store_action : sg_store_action; + clear_value : u8; +} + +sg_pass_action :: struct { + colors : [4]sg_color_attachment_action; + depth : sg_depth_attachment_action; + stencil : sg_stencil_attachment_action; +} + +sg_metal_swapchain :: struct { + current_drawable : *void; + depth_stencil_texture : *void; + msaa_color_texture : *void; +} + +sg_d3d11_swapchain :: struct { + render_view : *void; + resolve_view : *void; + depth_stencil_view : *void; +} + +sg_wgpu_swapchain :: struct { + render_view : *void; + resolve_view : *void; + depth_stencil_view : *void; +} + +sg_gl_swapchain :: struct { + framebuffer : u32; +} + +sg_swapchain :: struct { + width : s32; + height : s32; + sample_count : s32; + color_format : sg_pixel_format; + depth_format : sg_pixel_format; + metal : sg_metal_swapchain; + d3d11 : sg_d3d11_swapchain; + wgpu : sg_wgpu_swapchain; + gl : sg_gl_swapchain; +} + +sg_pass :: struct { + _ : u32; + compute : bool; + action : sg_pass_action; + attachments : sg_attachments; + swapchain : sg_swapchain; + label : *u8; + _ : u32; +} + +sg_bindings :: struct { + _ : u32; + vertex_buffers : [8]sg_buffer; + vertex_buffer_offsets : [8]s32; + index_buffer : sg_buffer; + index_buffer_offset : s32; + images : [16]sg_image; + samplers : [16]sg_sampler; + storage_buffers : [8]sg_buffer; + _ : u32; +} + +sg_buffer_desc :: struct { + _ : u32; + size : u64; + type : sg_buffer_type; + usage : sg_usage; + data : sg_range; + label : *u8; + gl_buffers : [2]u32; + mtl_buffers : [2]*void; + d3d11_buffer : *void; + wgpu_buffer : *void; + _ : u32; +} + +sg_image_data :: struct { + subimage : [6][16]sg_range; +} + +sg_image_desc :: struct { + _ : u32; + type : sg_image_type; + render_target : bool; + width : s32; + height : s32; + num_slices : s32; + num_mipmaps : s32; + usage : sg_usage; + pixel_format : sg_pixel_format; + sample_count : s32; + data : sg_image_data; + label : *u8; + gl_textures : [2]u32; + gl_texture_target : u32; + mtl_textures : [2]*void; + d3d11_texture : *void; + d3d11_shader_resource_view : *void; + wgpu_texture : *void; + wgpu_texture_view : *void; + _ : u32; +} + +sg_sampler_desc :: struct { + _ : u32; + min_filter : sg_filter; + mag_filter : sg_filter; + mipmap_filter : sg_filter; + wrap_u : sg_wrap; + wrap_v : sg_wrap; + wrap_w : sg_wrap; + min_lod : float; + max_lod : float; + border_color : sg_border_color; + compare : sg_compare_func; + max_anisotropy : u32; + label : *u8; + gl_sampler : u32; + mtl_sampler : *void; + d3d11_sampler : *void; + wgpu_sampler : *void; + _ : u32; +} + +/* + sg_shader_desc + + Used as parameter of sg_make_shader() to create a shader object which + communicates shader source or bytecode and shader interface + reflection information to sokol-gfx. + + If you use sokol-shdc you can ignore the following information since + the sg_shader_desc struct will be code generated. + + Otherwise you need to provide the following information to the + sg_make_shader() call: + + - a vertex- and fragment-shader function: + - the shader source or bytecode + - an optional entry point name + - for D3D11: an optional compile target when source code is provided + (the defaults are "vs_4_0" and "ps_4_0") + + - ...or alternatively, a compute function: + - the shader source or bytecode + - an optional entry point name + - for D3D11: an optional compile target when source code is provided + (the default is "cs_5_0") + + - vertex attributes required by some backends (not for compute shaders): + - the vertex attribute base type (undefined, float, signed int, unsigned int), + this information is only used in the validation layer to check that the + pipeline object vertex formats are compatible with the input vertex attribute + type used in the vertex shader. NOTE that the default base type + 'undefined' skips the validation layer check. + - for the GL backend: optional vertex attribute names used for name lookup + - for the D3D11 backend: semantic names and indices + + - only for compute shaders on the Metal backend: + - the workgroup size aka 'threads per thread-group' + + In other 3D APIs this is declared in the shader code: + - GLSL: `layout(local_size_x=x, local_size_y=y, local_size_y=z) in;` + - HLSL: `[numthreads(x, y, z)]` + - WGSL: `@workgroup_size(x, y, z)` + ...but in Metal the workgroup size is declared on the CPU side + + - reflection information for each uniform block used by the shader: + - the shader stage the uniform block appears in (SG_SHADERSTAGE_*) + - the size in bytes of the uniform block + - backend-specific bindslots: + - HLSL: the constant buffer register `register(b0..7)` + - MSL: the buffer attribute `[[buffer(0..7)]]` + - WGSL: the binding in `@group(0) @binding(0..15)` + - GLSL only: a description of the uniform block interior + - the memory layout standard (SG_UNIFORMLAYOUT_*) + - for each member in the uniform block: + - the member type (SG_UNIFORM_*) + - if the member is an array, the array count + - the member name + + - reflection information for each texture used by the shader: + - the shader stage the texture appears in (SG_SHADERSTAGE_*) + - the image type (SG_IMAGETYPE_*) + - the image-sample type (SG_IMAGESAMPLETYPE_*) + - whether the texture is multisampled + - backend specific bindslots: + - HLSL: the texture register `register(t0..23)` + - MSL: the texture attribute `[[texture(0..15)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + + - reflection information for each sampler used by the shader: + - the shader stage the sampler appears in (SG_SHADERSTAGE_*) + - the sampler type (SG_SAMPLERTYPE_*) + - backend specific bindslots: + - HLSL: the sampler register `register(s0..15)` + - MSL: the sampler attribute `[[sampler(0..15)]]` + - WGSL: the binding in `@group(0) @binding(0..127)` + + - reflection information for each storage buffer used by the shader: + - the shader stage the storage buffer appears in (SG_SHADERSTAGE_*) + - whether the storage buffer is readonly (currently this must + always be true) + - backend specific bindslots: + - HLSL: + - for readonly storage buffer bindings: `register(t0..23)` + - for read/write storage buffer bindings: `register(u0..7)` + - MSL: the buffer attribute `[[buffer(8..15)]]` + - WGSL: the binding in `@group(1) @binding(0..127)` + - GL: the binding in `layout(binding=0..7)` + + - reflection information for each combined image-sampler object + used by the shader: + - the shader stage (SG_SHADERSTAGE_*) + - the texture's array index in the sg_shader_desc.images[] array + - the sampler's array index in the sg_shader_desc.samplers[] array + - GLSL only: the name of the combined image-sampler object + + The number and order of items in the sg_shader_desc.attrs[] + array corresponds to the items in sg_pipeline_desc.layout.attrs. + + - sg_shader_desc.attrs[N] => sg_pipeline_desc.layout.attrs[N] + + NOTE that vertex attribute indices currently cannot have gaps. + + The items index in the sg_shader_desc.uniform_blocks[] array corresponds + to the ub_slot arg in sg_apply_uniforms(): + + - sg_shader_desc.uniform_blocks[N] => sg_apply_uniforms(N, ...) + + The items in the shader_desc images, samplers and storage_buffers + arrays correspond to the same array items in the sg_bindings struct: + + - sg_shader_desc.images[N] => sg_bindings.images[N] + - sg_shader_desc.samplers[N] => sg_bindings.samplers[N] + - sg_shader_desc.storage_buffers[N] => sg_bindings.storage_buffers[N] + + For all GL backends, shader source-code must be provided. For D3D11 and Metal, + either shader source-code or byte-code can be provided. + + NOTE that the uniform block, image, sampler and storage_buffer arrays + can have gaps. This allows to use the same sg_bindings struct for + different related shader variants. + + For D3D11, if source code is provided, the d3dcompiler_47.dll will be loaded + on demand. If this fails, shader creation will fail. When compiling HLSL + source code, you can provide an optional target string via + sg_shader_stage_desc.d3d11_target, the default target is "vs_4_0" for the + vertex shader stage and "ps_4_0" for the pixel shader stage. +*/ +sg_shader_stage :: enum u32 { + NONE; + VERTEX; + FRAGMENT; + COMPUTE; +} + +sg_shader_function :: struct { + source : *u8; + bytecode : sg_range; + entry : *u8; + d3d11_target : *u8; +} + +sg_shader_attr_base_type :: enum u32 { + UNDEFINED; + FLOAT; + SINT; + UINT; +} + +sg_shader_vertex_attr :: struct { + base_type : sg_shader_attr_base_type; + glsl_name : *u8; + hlsl_sem_name : *u8; + hlsl_sem_index : u8; +} + +sg_glsl_shader_uniform :: struct { + type : sg_uniform_type; + array_count : u16; + glsl_name : *u8; +} + +sg_shader_uniform_block :: struct { + stage : sg_shader_stage; + size : u32; + hlsl_register_b_n : u8; + msl_buffer_n : u8; + wgsl_group0_binding_n : u8; + layout : sg_uniform_layout; + glsl_uniforms : [16]sg_glsl_shader_uniform; +} + +sg_shader_image :: struct { + stage : sg_shader_stage; + image_type : sg_image_type; + sample_type : sg_image_sample_type; + multisampled : bool; + hlsl_register_t_n : u8; + msl_texture_n : u8; + wgsl_group1_binding_n : u8; +} + +sg_shader_sampler :: struct { + stage : sg_shader_stage; + sampler_type : sg_sampler_type; + hlsl_register_s_n : u8; + msl_sampler_n : u8; + wgsl_group1_binding_n : u8; +} + +sg_shader_storage_buffer :: struct { + stage : sg_shader_stage; + readonly : bool; + hlsl_register_t_n : u8; + hlsl_register_u_n : u8; + msl_buffer_n : u8; + wgsl_group1_binding_n : u8; + glsl_binding_n : u8; +} + +sg_shader_image_sampler_pair :: struct { + stage : sg_shader_stage; + image_slot : u8; + sampler_slot : u8; + glsl_name : *u8; +} + +sg_mtl_shader_threads_per_threadgroup :: struct { + x : s32; + y : s32; + z : s32; +} + +sg_shader_desc :: struct { + _ : u32; + vertex_func : sg_shader_function; + fragment_func : sg_shader_function; + compute_func : sg_shader_function; + attrs : [16]sg_shader_vertex_attr; + uniform_blocks : [8]sg_shader_uniform_block; + storage_buffers : [8]sg_shader_storage_buffer; + images : [16]sg_shader_image; + samplers : [16]sg_shader_sampler; + image_sampler_pairs : [16]sg_shader_image_sampler_pair; + mtl_threads_per_threadgroup : sg_mtl_shader_threads_per_threadgroup; + label : *u8; + _ : u32; +} + +sg_vertex_buffer_layout_state :: struct { + stride : s32; + step_func : sg_vertex_step; + step_rate : s32; +} + +sg_vertex_attr_state :: struct { + buffer_index : s32; + offset : s32; + format : sg_vertex_format; +} + +sg_vertex_layout_state :: struct { + buffers : [8]sg_vertex_buffer_layout_state; + attrs : [16]sg_vertex_attr_state; +} + +sg_stencil_face_state :: struct { + compare : sg_compare_func; + fail_op : sg_stencil_op; + depth_fail_op : sg_stencil_op; + pass_op : sg_stencil_op; +} + +sg_stencil_state :: struct { + enabled : bool; + front : sg_stencil_face_state; + back : sg_stencil_face_state; + read_mask : u8; + write_mask : u8; + ref : u8; +} + +sg_depth_state :: struct { + pixel_format : sg_pixel_format; + compare : sg_compare_func; + write_enabled : bool; + bias : float; + bias_slope_scale : float; + bias_clamp : float; +} + +sg_blend_state :: struct { + enabled : bool; + src_factor_rgb : sg_blend_factor; + dst_factor_rgb : sg_blend_factor; + op_rgb : sg_blend_op; + src_factor_alpha : sg_blend_factor; + dst_factor_alpha : sg_blend_factor; + op_alpha : sg_blend_op; +} + +sg_color_target_state :: struct { + pixel_format : sg_pixel_format; + write_mask : sg_color_mask; + blend : sg_blend_state; +} + +sg_pipeline_desc :: struct { + _ : u32; + compute : bool; + shader : sg_shader; + layout : sg_vertex_layout_state; + depth : sg_depth_state; + stencil : sg_stencil_state; + color_count : s32; + colors : [4]sg_color_target_state; + primitive_type : sg_primitive_type; + index_type : sg_index_type; + cull_mode : sg_cull_mode; + face_winding : sg_face_winding; + sample_count : s32; + blend_color : sg_color; + alpha_to_coverage_enabled : bool; + label : *u8; + _ : u32; +} + +sg_attachment_desc :: struct { + image : sg_image; + mip_level : s32; + slice : s32; +} + +sg_attachments_desc :: struct { + _ : u32; + colors : [4]sg_attachment_desc; + resolves : [4]sg_attachment_desc; + depth_stencil : sg_attachment_desc; + label : *u8; + _ : u32; +} + +sg_trace_hooks :: struct { + user_data : *void; + reset_state_cache : (a0: *void) #c_call; + make_buffer : (a0: *sg_buffer_desc, a1: sg_buffer, a2: *void) #c_call; + make_image : (a0: *sg_image_desc, a1: sg_image, a2: *void) #c_call; + make_sampler : (a0: *sg_sampler_desc, a1: sg_sampler, a2: *void) #c_call; + make_shader : (a0: *sg_shader_desc, a1: sg_shader, a2: *void) #c_call; + make_pipeline : (a0: *sg_pipeline_desc, a1: sg_pipeline, a2: *void) #c_call; + make_attachments : (a0: *sg_attachments_desc, a1: sg_attachments, a2: *void) #c_call; + destroy_buffer : (a0: sg_buffer, a1: *void) #c_call; + destroy_image : (a0: sg_image, a1: *void) #c_call; + destroy_sampler : (a0: sg_sampler, a1: *void) #c_call; + destroy_shader : (a0: sg_shader, a1: *void) #c_call; + destroy_pipeline : (a0: sg_pipeline, a1: *void) #c_call; + destroy_attachments : (a0: sg_attachments, a1: *void) #c_call; + update_buffer : (a0: sg_buffer, a1: *sg_range, a2: *void) #c_call; + update_image : (a0: sg_image, a1: *sg_image_data, a2: *void) #c_call; + append_buffer : (a0: sg_buffer, a1: *sg_range, a2: s32, a3: *void) #c_call; + begin_pass : (a0: *sg_pass, a1: *void) #c_call; + apply_viewport : (a0: s32, a1: s32, a2: s32, a3: s32, a4: bool, a5: *void) #c_call; + apply_scissor_rect : (a0: s32, a1: s32, a2: s32, a3: s32, a4: bool, a5: *void) #c_call; + apply_pipeline : (a0: sg_pipeline, a1: *void) #c_call; + apply_bindings : (a0: *sg_bindings, a1: *void) #c_call; + apply_uniforms : (a0: s32, a1: *sg_range, a2: *void) #c_call; + draw : (a0: s32, a1: s32, a2: s32, a3: *void) #c_call; + dispatch : (a0: s32, a1: s32, a2: s32, a3: *void) #c_call; + end_pass : (a0: *void) #c_call; + commit : (a0: *void) #c_call; + alloc_buffer : (a0: sg_buffer, a1: *void) #c_call; + alloc_image : (a0: sg_image, a1: *void) #c_call; + alloc_sampler : (a0: sg_sampler, a1: *void) #c_call; + alloc_shader : (a0: sg_shader, a1: *void) #c_call; + alloc_pipeline : (a0: sg_pipeline, a1: *void) #c_call; + alloc_attachments : (a0: sg_attachments, a1: *void) #c_call; + dealloc_buffer : (a0: sg_buffer, a1: *void) #c_call; + dealloc_image : (a0: sg_image, a1: *void) #c_call; + dealloc_sampler : (a0: sg_sampler, a1: *void) #c_call; + dealloc_shader : (a0: sg_shader, a1: *void) #c_call; + dealloc_pipeline : (a0: sg_pipeline, a1: *void) #c_call; + dealloc_attachments : (a0: sg_attachments, a1: *void) #c_call; + init_buffer : (a0: sg_buffer, a1: *sg_buffer_desc, a2: *void) #c_call; + init_image : (a0: sg_image, a1: *sg_image_desc, a2: *void) #c_call; + init_sampler : (a0: sg_sampler, a1: *sg_sampler_desc, a2: *void) #c_call; + init_shader : (a0: sg_shader, a1: *sg_shader_desc, a2: *void) #c_call; + init_pipeline : (a0: sg_pipeline, a1: *sg_pipeline_desc, a2: *void) #c_call; + init_attachments : (a0: sg_attachments, a1: *sg_attachments_desc, a2: *void) #c_call; + uninit_buffer : (a0: sg_buffer, a1: *void) #c_call; + uninit_image : (a0: sg_image, a1: *void) #c_call; + uninit_sampler : (a0: sg_sampler, a1: *void) #c_call; + uninit_shader : (a0: sg_shader, a1: *void) #c_call; + uninit_pipeline : (a0: sg_pipeline, a1: *void) #c_call; + uninit_attachments : (a0: sg_attachments, a1: *void) #c_call; + fail_buffer : (a0: sg_buffer, a1: *void) #c_call; + fail_image : (a0: sg_image, a1: *void) #c_call; + fail_sampler : (a0: sg_sampler, a1: *void) #c_call; + fail_shader : (a0: sg_shader, a1: *void) #c_call; + fail_pipeline : (a0: sg_pipeline, a1: *void) #c_call; + fail_attachments : (a0: sg_attachments, a1: *void) #c_call; + push_debug_group : (a0: *u8, a1: *void) #c_call; + pop_debug_group : (a0: *void) #c_call; +} + +sg_slot_info :: struct { + state : sg_resource_state; + res_id : u32; +} + +sg_buffer_info :: struct { + slot : sg_slot_info; + update_frame_index : u32; + append_frame_index : u32; + append_pos : s32; + append_overflow : bool; + num_slots : s32; + active_slot : s32; +} + +sg_image_info :: struct { + slot : sg_slot_info; + upd_frame_index : u32; + num_slots : s32; + active_slot : s32; +} + +sg_sampler_info :: struct { + slot : sg_slot_info; +} + +sg_shader_info :: struct { + slot : sg_slot_info; +} + +sg_pipeline_info :: struct { + slot : sg_slot_info; +} + +sg_attachments_info :: struct { + slot : sg_slot_info; +} + +sg_frame_stats_gl :: struct { + num_bind_buffer : u32; + num_active_texture : u32; + num_bind_texture : u32; + num_bind_sampler : u32; + num_use_program : u32; + num_render_state : u32; + num_vertex_attrib_pointer : u32; + num_vertex_attrib_divisor : u32; + num_enable_vertex_attrib_array : u32; + num_disable_vertex_attrib_array : u32; + num_uniform : u32; + num_memory_barriers : u32; +} + +sg_frame_stats_d3d11_pass :: struct { + num_om_set_render_targets : u32; + num_clear_render_target_view : u32; + num_clear_depth_stencil_view : u32; + num_resolve_subresource : u32; +} + +sg_frame_stats_d3d11_pipeline :: struct { + num_rs_set_state : u32; + num_om_set_depth_stencil_state : u32; + num_om_set_blend_state : u32; + num_ia_set_primitive_topology : u32; + num_ia_set_input_layout : u32; + num_vs_set_shader : u32; + num_vs_set_constant_buffers : u32; + num_ps_set_shader : u32; + num_ps_set_constant_buffers : u32; + num_cs_set_shader : u32; + num_cs_set_constant_buffers : u32; +} + +sg_frame_stats_d3d11_bindings :: struct { + num_ia_set_vertex_buffers : u32; + num_ia_set_index_buffer : u32; + num_vs_set_shader_resources : u32; + num_vs_set_samplers : u32; + num_ps_set_shader_resources : u32; + num_ps_set_samplers : u32; + num_cs_set_shader_resources : u32; + num_cs_set_samplers : u32; + num_cs_set_unordered_access_views : u32; +} + +sg_frame_stats_d3d11_uniforms :: struct { + num_update_subresource : u32; +} + +sg_frame_stats_d3d11_draw :: struct { + num_draw_indexed_instanced : u32; + num_draw_indexed : u32; + num_draw_instanced : u32; + num_draw : u32; +} + +sg_frame_stats_d3d11 :: struct { + pass : sg_frame_stats_d3d11_pass; + pipeline : sg_frame_stats_d3d11_pipeline; + bindings : sg_frame_stats_d3d11_bindings; + uniforms : sg_frame_stats_d3d11_uniforms; + draw : sg_frame_stats_d3d11_draw; + num_map : u32; + num_unmap : u32; +} + +sg_frame_stats_metal_idpool :: struct { + num_added : u32; + num_released : u32; + num_garbage_collected : u32; +} + +sg_frame_stats_metal_pipeline :: struct { + num_set_blend_color : u32; + num_set_cull_mode : u32; + num_set_front_facing_winding : u32; + num_set_stencil_reference_value : u32; + num_set_depth_bias : u32; + num_set_render_pipeline_state : u32; + num_set_depth_stencil_state : u32; +} + +sg_frame_stats_metal_bindings :: struct { + num_set_vertex_buffer : u32; + num_set_vertex_texture : u32; + num_set_vertex_sampler_state : u32; + num_set_fragment_buffer : u32; + num_set_fragment_texture : u32; + num_set_fragment_sampler_state : u32; + num_set_compute_buffer : u32; + num_set_compute_texture : u32; + num_set_compute_sampler_state : u32; +} + +sg_frame_stats_metal_uniforms :: struct { + num_set_vertex_buffer_offset : u32; + num_set_fragment_buffer_offset : u32; + num_set_compute_buffer_offset : u32; +} + +sg_frame_stats_metal :: struct { + idpool : sg_frame_stats_metal_idpool; + pipeline : sg_frame_stats_metal_pipeline; + bindings : sg_frame_stats_metal_bindings; + uniforms : sg_frame_stats_metal_uniforms; +} + +sg_frame_stats_wgpu_uniforms :: struct { + num_set_bindgroup : u32; + size_write_buffer : u32; +} + +sg_frame_stats_wgpu_bindings :: struct { + num_set_vertex_buffer : u32; + num_skip_redundant_vertex_buffer : u32; + num_set_index_buffer : u32; + num_skip_redundant_index_buffer : u32; + num_create_bindgroup : u32; + num_discard_bindgroup : u32; + num_set_bindgroup : u32; + num_skip_redundant_bindgroup : u32; + num_bindgroup_cache_hits : u32; + num_bindgroup_cache_misses : u32; + num_bindgroup_cache_collisions : u32; + num_bindgroup_cache_invalidates : u32; + num_bindgroup_cache_hash_vs_key_mismatch : u32; +} + +sg_frame_stats_wgpu :: struct { + uniforms : sg_frame_stats_wgpu_uniforms; + bindings : sg_frame_stats_wgpu_bindings; +} + +sg_frame_stats :: struct { + frame_index : u32; + num_passes : u32; + num_apply_viewport : u32; + num_apply_scissor_rect : u32; + num_apply_pipeline : u32; + num_apply_bindings : u32; + num_apply_uniforms : u32; + num_draw : u32; + num_dispatch : u32; + num_update_buffer : u32; + num_append_buffer : u32; + num_update_image : u32; + size_apply_uniforms : u32; + size_update_buffer : u32; + size_append_buffer : u32; + size_update_image : u32; + gl : sg_frame_stats_gl; + d3d11 : sg_frame_stats_d3d11; + metal : sg_frame_stats_metal; + wgpu : sg_frame_stats_wgpu; +} + +sg_log_item :: enum u32 { + OK; + MALLOC_FAILED; + GL_TEXTURE_FORMAT_NOT_SUPPORTED; + GL_3D_TEXTURES_NOT_SUPPORTED; + GL_ARRAY_TEXTURES_NOT_SUPPORTED; + GL_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE; + GL_SHADER_COMPILATION_FAILED; + GL_SHADER_LINKING_FAILED; + GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER; + GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER; + GL_IMAGE_SAMPLER_NAME_NOT_FOUND_IN_SHADER; + GL_FRAMEBUFFER_STATUS_UNDEFINED; + GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT; + GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT; + GL_FRAMEBUFFER_STATUS_UNSUPPORTED; + GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE; + GL_FRAMEBUFFER_STATUS_UNKNOWN; + D3D11_CREATE_BUFFER_FAILED; + D3D11_CREATE_BUFFER_SRV_FAILED; + D3D11_CREATE_BUFFER_UAV_FAILED; + D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT; + D3D11_CREATE_DEPTH_TEXTURE_FAILED; + D3D11_CREATE_2D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT; + D3D11_CREATE_2D_TEXTURE_FAILED; + D3D11_CREATE_2D_SRV_FAILED; + D3D11_CREATE_3D_TEXTURE_UNSUPPORTED_PIXEL_FORMAT; + D3D11_CREATE_3D_TEXTURE_FAILED; + D3D11_CREATE_3D_SRV_FAILED; + D3D11_CREATE_MSAA_TEXTURE_FAILED; + D3D11_CREATE_SAMPLER_STATE_FAILED; + D3D11_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE; + D3D11_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE; + D3D11_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE; + D3D11_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE; + D3D11_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE; + D3D11_LOAD_D3DCOMPILER_47_DLL_FAILED; + D3D11_SHADER_COMPILATION_FAILED; + D3D11_SHADER_COMPILATION_OUTPUT; + D3D11_CREATE_CONSTANT_BUFFER_FAILED; + D3D11_CREATE_INPUT_LAYOUT_FAILED; + D3D11_CREATE_RASTERIZER_STATE_FAILED; + D3D11_CREATE_DEPTH_STENCIL_STATE_FAILED; + D3D11_CREATE_BLEND_STATE_FAILED; + D3D11_CREATE_RTV_FAILED; + D3D11_CREATE_DSV_FAILED; + D3D11_MAP_FOR_UPDATE_BUFFER_FAILED; + D3D11_MAP_FOR_APPEND_BUFFER_FAILED; + D3D11_MAP_FOR_UPDATE_IMAGE_FAILED; + METAL_CREATE_BUFFER_FAILED; + METAL_TEXTURE_FORMAT_NOT_SUPPORTED; + METAL_CREATE_TEXTURE_FAILED; + METAL_CREATE_SAMPLER_FAILED; + METAL_SHADER_COMPILATION_FAILED; + METAL_SHADER_CREATION_FAILED; + METAL_SHADER_COMPILATION_OUTPUT; + METAL_SHADER_ENTRY_NOT_FOUND; + METAL_UNIFORMBLOCK_MSL_BUFFER_SLOT_OUT_OF_RANGE; + METAL_STORAGEBUFFER_MSL_BUFFER_SLOT_OUT_OF_RANGE; + METAL_IMAGE_MSL_TEXTURE_SLOT_OUT_OF_RANGE; + METAL_SAMPLER_MSL_SAMPLER_SLOT_OUT_OF_RANGE; + METAL_CREATE_CPS_FAILED; + METAL_CREATE_CPS_OUTPUT; + METAL_CREATE_RPS_FAILED; + METAL_CREATE_RPS_OUTPUT; + METAL_CREATE_DSS_FAILED; + WGPU_BINDGROUPS_POOL_EXHAUSTED; + WGPU_BINDGROUPSCACHE_SIZE_GREATER_ONE; + WGPU_BINDGROUPSCACHE_SIZE_POW2; + WGPU_CREATEBINDGROUP_FAILED; + WGPU_CREATE_BUFFER_FAILED; + WGPU_CREATE_TEXTURE_FAILED; + WGPU_CREATE_TEXTURE_VIEW_FAILED; + WGPU_CREATE_SAMPLER_FAILED; + WGPU_CREATE_SHADER_MODULE_FAILED; + WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED; + WGPU_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE; + WGPU_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE; + WGPU_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE; + WGPU_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE; + WGPU_CREATE_PIPELINE_LAYOUT_FAILED; + WGPU_CREATE_RENDER_PIPELINE_FAILED; + WGPU_CREATE_COMPUTE_PIPELINE_FAILED; + WGPU_ATTACHMENTS_CREATE_TEXTURE_VIEW_FAILED; + IDENTICAL_COMMIT_LISTENER; + COMMIT_LISTENER_ARRAY_FULL; + TRACE_HOOKS_NOT_ENABLED; + DEALLOC_BUFFER_INVALID_STATE; + DEALLOC_IMAGE_INVALID_STATE; + DEALLOC_SAMPLER_INVALID_STATE; + DEALLOC_SHADER_INVALID_STATE; + DEALLOC_PIPELINE_INVALID_STATE; + DEALLOC_ATTACHMENTS_INVALID_STATE; + INIT_BUFFER_INVALID_STATE; + INIT_IMAGE_INVALID_STATE; + INIT_SAMPLER_INVALID_STATE; + INIT_SHADER_INVALID_STATE; + INIT_PIPELINE_INVALID_STATE; + INIT_ATTACHMENTS_INVALID_STATE; + UNINIT_BUFFER_INVALID_STATE; + UNINIT_IMAGE_INVALID_STATE; + UNINIT_SAMPLER_INVALID_STATE; + UNINIT_SHADER_INVALID_STATE; + UNINIT_PIPELINE_INVALID_STATE; + UNINIT_ATTACHMENTS_INVALID_STATE; + FAIL_BUFFER_INVALID_STATE; + FAIL_IMAGE_INVALID_STATE; + FAIL_SAMPLER_INVALID_STATE; + FAIL_SHADER_INVALID_STATE; + FAIL_PIPELINE_INVALID_STATE; + FAIL_ATTACHMENTS_INVALID_STATE; + BUFFER_POOL_EXHAUSTED; + IMAGE_POOL_EXHAUSTED; + SAMPLER_POOL_EXHAUSTED; + SHADER_POOL_EXHAUSTED; + PIPELINE_POOL_EXHAUSTED; + PASS_POOL_EXHAUSTED; + BEGINPASS_ATTACHMENT_INVALID; + APPLY_BINDINGS_STORAGE_BUFFER_TRACKER_EXHAUSTED; + DRAW_WITHOUT_BINDINGS; + VALIDATE_BUFFERDESC_CANARY; + VALIDATE_BUFFERDESC_EXPECT_NONZERO_SIZE; + VALIDATE_BUFFERDESC_EXPECT_MATCHING_DATA_SIZE; + VALIDATE_BUFFERDESC_EXPECT_ZERO_DATA_SIZE; + VALIDATE_BUFFERDESC_EXPECT_NO_DATA; + VALIDATE_BUFFERDESC_STORAGEBUFFER_SUPPORTED; + VALIDATE_BUFFERDESC_STORAGEBUFFER_SIZE_MULTIPLE_4; + VALIDATE_IMAGEDATA_NODATA; + VALIDATE_IMAGEDATA_DATA_SIZE; + VALIDATE_IMAGEDESC_CANARY; + VALIDATE_IMAGEDESC_WIDTH; + VALIDATE_IMAGEDESC_HEIGHT; + VALIDATE_IMAGEDESC_RT_PIXELFORMAT; + VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT; + VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT; + VALIDATE_IMAGEDESC_NO_MSAA_RT_SUPPORT; + VALIDATE_IMAGEDESC_MSAA_NUM_MIPMAPS; + VALIDATE_IMAGEDESC_MSAA_3D_IMAGE; + VALIDATE_IMAGEDESC_MSAA_CUBE_IMAGE; + VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE; + VALIDATE_IMAGEDESC_RT_IMMUTABLE; + VALIDATE_IMAGEDESC_RT_NO_DATA; + VALIDATE_IMAGEDESC_INJECTED_NO_DATA; + VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA; + VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE; + VALIDATE_SAMPLERDESC_CANARY; + VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING; + VALIDATE_SHADERDESC_CANARY; + VALIDATE_SHADERDESC_VERTEX_SOURCE; + VALIDATE_SHADERDESC_FRAGMENT_SOURCE; + VALIDATE_SHADERDESC_COMPUTE_SOURCE; + VALIDATE_SHADERDESC_VERTEX_SOURCE_OR_BYTECODE; + VALIDATE_SHADERDESC_FRAGMENT_SOURCE_OR_BYTECODE; + VALIDATE_SHADERDESC_COMPUTE_SOURCE_OR_BYTECODE; + VALIDATE_SHADERDESC_INVALID_SHADER_COMBO; + VALIDATE_SHADERDESC_NO_BYTECODE_SIZE; + VALIDATE_SHADERDESC_METAL_THREADS_PER_THREADGROUP; + VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_CONT_MEMBERS; + VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_IS_ZERO; + VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_OUT_OF_RANGE; + VALIDATE_SHADERDESC_UNIFORMBLOCK_METAL_BUFFER_SLOT_COLLISION; + VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_OUT_OF_RANGE; + VALIDATE_SHADERDESC_UNIFORMBLOCK_HLSL_REGISTER_B_COLLISION; + VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_OUT_OF_RANGE; + VALIDATE_SHADERDESC_UNIFORMBLOCK_WGSL_GROUP0_BINDING_COLLISION; + VALIDATE_SHADERDESC_UNIFORMBLOCK_NO_MEMBERS; + VALIDATE_SHADERDESC_UNIFORMBLOCK_UNIFORM_GLSL_NAME; + VALIDATE_SHADERDESC_UNIFORMBLOCK_SIZE_MISMATCH; + VALIDATE_SHADERDESC_UNIFORMBLOCK_ARRAY_COUNT; + VALIDATE_SHADERDESC_UNIFORMBLOCK_STD140_ARRAY_TYPE; + VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_OUT_OF_RANGE; + VALIDATE_SHADERDESC_STORAGEBUFFER_METAL_BUFFER_SLOT_COLLISION; + VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_OUT_OF_RANGE; + VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_T_COLLISION; + VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_OUT_OF_RANGE; + VALIDATE_SHADERDESC_STORAGEBUFFER_HLSL_REGISTER_U_COLLISION; + VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_OUT_OF_RANGE; + VALIDATE_SHADERDESC_STORAGEBUFFER_GLSL_BINDING_COLLISION; + VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_OUT_OF_RANGE; + VALIDATE_SHADERDESC_STORAGEBUFFER_WGSL_GROUP1_BINDING_COLLISION; + VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_OUT_OF_RANGE; + VALIDATE_SHADERDESC_IMAGE_METAL_TEXTURE_SLOT_COLLISION; + VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_OUT_OF_RANGE; + VALIDATE_SHADERDESC_IMAGE_HLSL_REGISTER_T_COLLISION; + VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_OUT_OF_RANGE; + VALIDATE_SHADERDESC_IMAGE_WGSL_GROUP1_BINDING_COLLISION; + VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_OUT_OF_RANGE; + VALIDATE_SHADERDESC_SAMPLER_METAL_SAMPLER_SLOT_COLLISION; + VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_OUT_OF_RANGE; + VALIDATE_SHADERDESC_SAMPLER_HLSL_REGISTER_S_COLLISION; + VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_OUT_OF_RANGE; + VALIDATE_SHADERDESC_SAMPLER_WGSL_GROUP1_BINDING_COLLISION; + VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE; + VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE; + VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_STAGE_MISMATCH; + VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_STAGE_MISMATCH; + VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_GLSL_NAME; + VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED; + VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED; + VALIDATE_SHADERDESC_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS; + VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS; + VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG; + VALIDATE_PIPELINEDESC_CANARY; + VALIDATE_PIPELINEDESC_SHADER; + VALIDATE_PIPELINEDESC_COMPUTE_SHADER_EXPECTED; + VALIDATE_PIPELINEDESC_NO_COMPUTE_SHADER_EXPECTED; + VALIDATE_PIPELINEDESC_NO_CONT_ATTRS; + VALIDATE_PIPELINEDESC_ATTR_BASETYPE_MISMATCH; + VALIDATE_PIPELINEDESC_LAYOUT_STRIDE4; + VALIDATE_PIPELINEDESC_ATTR_SEMANTICS; + VALIDATE_PIPELINEDESC_SHADER_READONLY_STORAGEBUFFERS; + VALIDATE_PIPELINEDESC_BLENDOP_MINMAX_REQUIRES_BLENDFACTOR_ONE; + VALIDATE_ATTACHMENTSDESC_CANARY; + VALIDATE_ATTACHMENTSDESC_NO_ATTACHMENTS; + VALIDATE_ATTACHMENTSDESC_NO_CONT_COLOR_ATTS; + VALIDATE_ATTACHMENTSDESC_IMAGE; + VALIDATE_ATTACHMENTSDESC_MIPLEVEL; + VALIDATE_ATTACHMENTSDESC_FACE; + VALIDATE_ATTACHMENTSDESC_LAYER; + VALIDATE_ATTACHMENTSDESC_SLICE; + VALIDATE_ATTACHMENTSDESC_IMAGE_NO_RT; + VALIDATE_ATTACHMENTSDESC_COLOR_INV_PIXELFORMAT; + VALIDATE_ATTACHMENTSDESC_DEPTH_INV_PIXELFORMAT; + VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES; + VALIDATE_ATTACHMENTSDESC_IMAGE_SAMPLE_COUNTS; + VALIDATE_ATTACHMENTSDESC_RESOLVE_COLOR_IMAGE_MSAA; + VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE; + VALIDATE_ATTACHMENTSDESC_RESOLVE_SAMPLE_COUNT; + VALIDATE_ATTACHMENTSDESC_RESOLVE_MIPLEVEL; + VALIDATE_ATTACHMENTSDESC_RESOLVE_FACE; + VALIDATE_ATTACHMENTSDESC_RESOLVE_LAYER; + VALIDATE_ATTACHMENTSDESC_RESOLVE_SLICE; + VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_NO_RT; + VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES; + VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_FORMAT; + VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE; + VALIDATE_ATTACHMENTSDESC_DEPTH_MIPLEVEL; + VALIDATE_ATTACHMENTSDESC_DEPTH_FACE; + VALIDATE_ATTACHMENTSDESC_DEPTH_LAYER; + VALIDATE_ATTACHMENTSDESC_DEPTH_SLICE; + VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_NO_RT; + VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES; + VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SAMPLE_COUNT; + VALIDATE_BEGINPASS_CANARY; + VALIDATE_BEGINPASS_EXPECT_NO_ATTACHMENTS; + VALIDATE_BEGINPASS_ATTACHMENTS_EXISTS; + VALIDATE_BEGINPASS_ATTACHMENTS_VALID; + VALIDATE_BEGINPASS_COLOR_ATTACHMENT_IMAGE; + VALIDATE_BEGINPASS_RESOLVE_ATTACHMENT_IMAGE; + VALIDATE_BEGINPASS_DEPTHSTENCIL_ATTACHMENT_IMAGE; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_WIDTH_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_HEIGHT_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_SAMPLECOUNT_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_COLORFORMAT_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_EXPECT_DEPTHFORMAT_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE; + VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_CURRENTDRAWABLE_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE; + VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_DEPTHSTENCILTEXTURE_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE; + VALIDATE_BEGINPASS_SWAPCHAIN_METAL_EXPECT_MSAACOLORTEXTURE_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW; + VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RENDERVIEW_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW; + VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_RESOLVEVIEW_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW; + VALIDATE_BEGINPASS_SWAPCHAIN_D3D11_EXPECT_DEPTHSTENCILVIEW_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW; + VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RENDERVIEW_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW; + VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_RESOLVEVIEW_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW; + VALIDATE_BEGINPASS_SWAPCHAIN_WGPU_EXPECT_DEPTHSTENCILVIEW_NOTSET; + VALIDATE_BEGINPASS_SWAPCHAIN_GL_EXPECT_FRAMEBUFFER_NOTSET; + VALIDATE_AVP_RENDERPASS_EXPECTED; + VALIDATE_ASR_RENDERPASS_EXPECTED; + VALIDATE_APIP_PIPELINE_VALID_ID; + VALIDATE_APIP_PIPELINE_EXISTS; + VALIDATE_APIP_PIPELINE_VALID; + VALIDATE_APIP_PASS_EXPECTED; + VALIDATE_APIP_SHADER_EXISTS; + VALIDATE_APIP_SHADER_VALID; + VALIDATE_APIP_COMPUTEPASS_EXPECTED; + VALIDATE_APIP_RENDERPASS_EXPECTED; + VALIDATE_APIP_CURPASS_ATTACHMENTS_EXISTS; + VALIDATE_APIP_CURPASS_ATTACHMENTS_VALID; + VALIDATE_APIP_ATT_COUNT; + VALIDATE_APIP_COLOR_FORMAT; + VALIDATE_APIP_DEPTH_FORMAT; + VALIDATE_APIP_SAMPLE_COUNT; + VALIDATE_ABND_PASS_EXPECTED; + VALIDATE_ABND_EMPTY_BINDINGS; + VALIDATE_ABND_PIPELINE; + VALIDATE_ABND_PIPELINE_EXISTS; + VALIDATE_ABND_PIPELINE_VALID; + VALIDATE_ABND_COMPUTE_EXPECTED_NO_VBS; + VALIDATE_ABND_COMPUTE_EXPECTED_NO_IB; + VALIDATE_ABND_EXPECTED_VB; + VALIDATE_ABND_VB_EXISTS; + VALIDATE_ABND_VB_TYPE; + VALIDATE_ABND_VB_OVERFLOW; + VALIDATE_ABND_NO_IB; + VALIDATE_ABND_IB; + VALIDATE_ABND_IB_EXISTS; + VALIDATE_ABND_IB_TYPE; + VALIDATE_ABND_IB_OVERFLOW; + VALIDATE_ABND_EXPECTED_IMAGE_BINDING; + VALIDATE_ABND_IMG_EXISTS; + VALIDATE_ABND_IMAGE_TYPE_MISMATCH; + VALIDATE_ABND_EXPECTED_MULTISAMPLED_IMAGE; + VALIDATE_ABND_IMAGE_MSAA; + VALIDATE_ABND_EXPECTED_FILTERABLE_IMAGE; + VALIDATE_ABND_EXPECTED_DEPTH_IMAGE; + VALIDATE_ABND_EXPECTED_SAMPLER_BINDING; + VALIDATE_ABND_UNEXPECTED_SAMPLER_COMPARE_NEVER; + VALIDATE_ABND_EXPECTED_SAMPLER_COMPARE_NEVER; + VALIDATE_ABND_EXPECTED_NONFILTERING_SAMPLER; + VALIDATE_ABND_SMP_EXISTS; + VALIDATE_ABND_EXPECTED_STORAGEBUFFER_BINDING; + VALIDATE_ABND_STORAGEBUFFER_EXISTS; + VALIDATE_ABND_STORAGEBUFFER_BINDING_BUFFERTYPE; + VALIDATE_ABND_STORAGEBUFFER_READWRITE_IMMUTABLE; + VALIDATE_AU_PASS_EXPECTED; + VALIDATE_AU_NO_PIPELINE; + VALIDATE_AU_NO_UNIFORMBLOCK_AT_SLOT; + VALIDATE_AU_SIZE; + VALIDATE_DRAW_RENDERPASS_EXPECTED; + VALIDATE_DRAW_BASEELEMENT; + VALIDATE_DRAW_NUMELEMENTS; + VALIDATE_DRAW_NUMINSTANCES; + VALIDATE_DRAW_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING; + VALIDATE_DISPATCH_COMPUTEPASS_EXPECTED; + VALIDATE_DISPATCH_NUMGROUPSX; + VALIDATE_DISPATCH_NUMGROUPSY; + VALIDATE_DISPATCH_NUMGROUPSZ; + VALIDATE_DISPATCH_REQUIRED_BINDINGS_OR_UNIFORMS_MISSING; + VALIDATE_UPDATEBUF_USAGE; + VALIDATE_UPDATEBUF_SIZE; + VALIDATE_UPDATEBUF_ONCE; + VALIDATE_UPDATEBUF_APPEND; + VALIDATE_APPENDBUF_USAGE; + VALIDATE_APPENDBUF_SIZE; + VALIDATE_APPENDBUF_UPDATE; + VALIDATE_UPDIMG_USAGE; + VALIDATE_UPDIMG_ONCE; + VALIDATION_FAILED; +} + +sg_environment_defaults :: struct { + color_format : sg_pixel_format; + depth_format : sg_pixel_format; + sample_count : s32; +} + +sg_metal_environment :: struct { + device : *void; +} + +sg_d3d11_environment :: struct { + device : *void; + device_context : *void; +} + +sg_wgpu_environment :: struct { + device : *void; +} + +sg_environment :: struct { + defaults : sg_environment_defaults; + metal : sg_metal_environment; + d3d11 : sg_d3d11_environment; + wgpu : sg_wgpu_environment; +} + +sg_commit_listener :: struct { + func : (a0: *void) #c_call; + user_data : *void; +} + +sg_allocator :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +sg_logger :: struct { + func : (a0: *u8, a1: u32, a2: u32, a3: *u8, a4: u32, a5: *u8, a6: *void) #c_call; + user_data : *void; +} + +sg_desc :: struct { + _ : u32; + buffer_pool_size : s32; + image_pool_size : s32; + sampler_pool_size : s32; + shader_pool_size : s32; + pipeline_pool_size : s32; + attachments_pool_size : s32; + uniform_buffer_size : s32; + max_dispatch_calls_per_pass : s32; + max_commit_listeners : s32; + disable_validation : bool; + d3d11_shader_debugging : bool; + mtl_force_managed_storage_mode : bool; + mtl_use_command_buffer_with_retained_references : bool; + wgpu_disable_bindgroups_cache : bool; + wgpu_bindgroups_cache_size : s32; + allocator : sg_allocator; + logger : sg_logger; + environment : sg_environment; + _ : u32; +} + +sg_d3d11_buffer_info :: struct { + buf : *void; +} + +sg_d3d11_image_info :: struct { + tex2d : *void; + tex3d : *void; + res : *void; + srv : *void; +} + +sg_d3d11_sampler_info :: struct { + smp : *void; +} + +sg_d3d11_shader_info :: struct { + cbufs : [8]*void; + vs : *void; + fs : *void; +} + +sg_d3d11_pipeline_info :: struct { + il : *void; + rs : *void; + dss : *void; + bs : *void; +} + +sg_d3d11_attachments_info :: struct { + color_rtv : [4]*void; + resolve_rtv : [4]*void; + dsv : *void; +} + +sg_mtl_buffer_info :: struct { + buf : [2]*void; + active_slot : s32; +} + +sg_mtl_image_info :: struct { + tex : [2]*void; + active_slot : s32; +} + +sg_mtl_sampler_info :: struct { + smp : *void; +} + +sg_mtl_shader_info :: struct { + vertex_lib : *void; + fragment_lib : *void; + vertex_func : *void; + fragment_func : *void; +} + +sg_mtl_pipeline_info :: struct { + rps : *void; + dss : *void; +} + +sg_wgpu_buffer_info :: struct { + buf : *void; +} + +sg_wgpu_image_info :: struct { + tex : *void; + view : *void; +} + +sg_wgpu_sampler_info :: struct { + smp : *void; +} + +sg_wgpu_shader_info :: struct { + vs_mod : *void; + fs_mod : *void; + bgl : *void; +} + +sg_wgpu_pipeline_info :: struct { + render_pipeline : *void; + compute_pipeline : *void; +} + +sg_wgpu_attachments_info :: struct { + color_view : [4]*void; + resolve_view : [4]*void; + ds_view : *void; +} + +sg_gl_buffer_info :: struct { + buf : [2]u32; + active_slot : s32; +} + +sg_gl_image_info :: struct { + tex : [2]u32; + tex_target : u32; + msaa_render_buffer : u32; + active_slot : s32; +} + +sg_gl_sampler_info :: struct { + smp : u32; +} + +sg_gl_shader_info :: struct { + prog : u32; +} + +sg_gl_attachments_info :: struct { + framebuffer : u32; + msaa_resolve_framebuffer : [4]u32; +} + diff --git a/modules/sokol-jai/sokol/gl/module.jai b/modules/sokol-jai/sokol/gl/module.jai new file mode 100644 index 0000000..49cebe5 --- /dev/null +++ b/modules/sokol-jai/sokol/gl/module.jai @@ -0,0 +1,924 @@ +// machine generated, do not edit + +/* + + sokol_gl.h -- OpenGL 1.x style rendering on top of sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GL_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_GL_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GL_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + If sokol_gl.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GL_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before including sokol_gl.h: + + sokol_gfx.h + + Matrix functions have been taken from MESA and Regal. + + FEATURE OVERVIEW: + ================= + sokol_gl.h implements a subset of the OpenGLES 1.x feature set useful for + when you just want to quickly render a bunch of triangles or + lines without having to mess with buffers and shaders. + + The current feature set is mostly useful for debug visualizations + and simple UI-style 2D rendering: + + What's implemented: + - vertex components: + - position (x, y, z) + - 2D texture coords (u, v) + - color (r, g, b, a) + - primitive types: + - triangle list and strip + - line list and strip + - quad list (TODO: quad strips) + - point list + - one texture layer (no multi-texturing) + - viewport and scissor-rect with selectable origin (top-left or bottom-left) + - all GL 1.x matrix stack functions, and additionally equivalent + functions for gluPerspective and gluLookat + + Notable GLES 1.x features that are *NOT* implemented: + - vertex lighting (this is the most likely GL feature that might be added later) + - vertex arrays (although providing whole chunks of vertex data at once + might be a useful feature for a later version) + - texture coordinate generation + - line width + - all pixel store functions + - no ALPHA_TEST + - no clear functions (clearing is handled by the sokol-gfx render pass) + - fog + + Notable differences to GL: + - No "enum soup" for render states etc, instead there's a + 'pipeline stack', this is similar to GL's matrix stack, + but for pipeline-state-objects. The pipeline object at + the top of the pipeline stack defines the active set of render states + - All angles are in radians, not degrees (note the sgl_rad() and + sgl_deg() conversion functions) + - No enable/disable state for scissor test, this is always enabled + + STEP BY STEP: + ============= + --- To initialize sokol-gl, call: + + sgl_setup(const sgl_desc_t* desc) + + NOTE that sgl_setup() must be called *after* initializing sokol-gfx + (via sg_setup). This is because sgl_setup() needs to create + sokol-gfx resource objects. + + If you're intending to render to the default pass, and also don't + want to tweak memory usage, and don't want any logging output you can + just keep sgl_desc_t zero-initialized: + + sgl_setup(&(sgl_desc_t*){ 0 }); + + In this case, sokol-gl will create internal sg_pipeline objects that + are compatible with the sokol-app default framebuffer. + + I would recommend to at least install a logging callback so that + you'll see any warnings and errors. The easiest way is through + sokol_log.h: + + #include "sokol_log.h" + + sgl_setup(&(sgl_desc_t){ + .logger.func = slog_func. + }); + + If you want to render into a framebuffer with different pixel-format + and MSAA attributes you need to provide the matching attributes in the + sgl_setup() call: + + sgl_setup(&(sgl_desc_t*){ + .color_format = SG_PIXELFORMAT_..., + .depth_format = SG_PIXELFORMAT_..., + .sample_count = ..., + }); + + To reduce memory usage, or if you need to create more then the default number of + contexts, pipelines, vertices or draw commands, set the following sgl_desc_t + members: + + .context_pool_size (default: 4) + .pipeline_pool_size (default: 64) + .max_vertices (default: 64k) + .max_commands (default: 16k) + + Finally you can change the face winding for front-facing triangles + and quads: + + .face_winding - default is SG_FACEWINDING_CCW + + The default winding for front faces is counter-clock-wise. This is + the same as OpenGL's default, but different from sokol-gfx. + + --- Optionally create additional context objects if you want to render into + multiple sokol-gfx render passes (or generally if you want to + use multiple independent sokol-gl "state buckets") + + sgl_context ctx = sgl_make_context(const sgl_context_desc_t* desc) + + For details on rendering with sokol-gl contexts, search below for + WORKING WITH CONTEXTS. + + --- Optionally create pipeline-state-objects if you need render state + that differs from sokol-gl's default state: + + sgl_pipeline pip = sgl_make_pipeline(const sg_pipeline_desc* desc) + + ...this creates a pipeline object that's compatible with the currently + active context, alternatively call: + + sgl_pipeline_pip = sgl_context_make_pipeline(sgl_context ctx, const sg_pipeline_desc* desc) + + ...to create a pipeline object that's compatible with an explicitly + provided context. + + The similarity with sokol_gfx.h's sg_pipeline type and sg_make_pipeline() + function is intended. sgl_make_pipeline() also takes a standard + sokol-gfx sg_pipeline_desc object to describe the render state, but + without: + - shader + - vertex layout + - color- and depth-pixel-formats + - primitive type (lines, triangles, ...) + - MSAA sample count + Those will be filled in by sgl_make_pipeline(). Note that each + call to sgl_make_pipeline() needs to create several sokol-gfx + pipeline objects (one for each primitive type). + + 'depth.write_enabled' will be forced to 'false' if the context this + pipeline object is intended for has its depth pixel format set to + SG_PIXELFORMAT_NONE (which means the framebuffer this context is used + with doesn't have a depth-stencil surface). + + --- if you need to destroy sgl_pipeline objects before sgl_shutdown(): + + sgl_destroy_pipeline(sgl_pipeline pip) + + --- After sgl_setup() you can call any of the sokol-gl functions anywhere + in a frame, *except* sgl_draw(). The 'vanilla' functions + will only change internal sokol-gl state, and not call any sokol-gfx + functions. + + --- Unlike OpenGL, sokol-gl has a function to reset internal state to + a known default. This is useful at the start of a sequence of + rendering operations: + + void sgl_defaults(void) + + This will set the following default state: + + - current texture coordinate to u=0.0f, v=0.0f + - current color to white (rgba all 1.0f) + - current point size to 1.0f + - unbind the current texture and texturing will be disabled + - *all* matrices will be set to identity (also the projection matrix) + - the default render state will be set by loading the 'default pipeline' + into the top of the pipeline stack + + The current matrix- and pipeline-stack-depths will not be changed by + sgl_defaults(). + + --- change the currently active renderstate through the + pipeline-stack functions, this works similar to the + traditional GL matrix stack: + + ...load the default pipeline state on the top of the pipeline stack: + + sgl_load_default_pipeline() + + ...load a specific pipeline on the top of the pipeline stack: + + sgl_load_pipeline(sgl_pipeline pip) + + ...push and pop the pipeline stack: + sgl_push_pipeline() + sgl_pop_pipeline() + + --- control texturing with: + + sgl_enable_texture() + sgl_disable_texture() + sgl_texture(sg_image img, sg_sampler smp) + + NOTE: the img and smp handles can be invalid (SG_INVALID_ID), in this + case, sokol-gl will fall back to the internal default (white) texture + and sampler. + + --- set the current viewport and scissor rect with: + + sgl_viewport(int x, int y, int w, int h, bool origin_top_left) + sgl_scissor_rect(int x, int y, int w, int h, bool origin_top_left) + + ...or call these alternatives which take float arguments (this might allow + to avoid casting between float and integer in more strongly typed languages + when floating point pixel coordinates are used): + + sgl_viewportf(float x, float y, float w, float h, bool origin_top_left) + sgl_scissor_rectf(float x, float y, float w, float h, bool origin_top_left) + + ...these calls add a new command to the internal command queue, so + that the viewport or scissor rect are set at the right time relative + to other sokol-gl calls. + + --- adjust the transform matrices, matrix manipulation works just like + the OpenGL matrix stack: + + ...set the current matrix mode: + + sgl_matrix_mode_modelview() + sgl_matrix_mode_projection() + sgl_matrix_mode_texture() + + ...load the identity matrix into the current matrix: + + sgl_load_identity() + + ...translate, rotate and scale the current matrix: + + sgl_translate(float x, float y, float z) + sgl_rotate(float angle_rad, float x, float y, float z) + sgl_scale(float x, float y, float z) + + NOTE that all angles in sokol-gl are in radians, not in degree. + Convert between radians and degree with the helper functions: + + float sgl_rad(float deg) - degrees to radians + float sgl_deg(float rad) - radians to degrees + + ...directly load the current matrix from a float[16] array: + + sgl_load_matrix(const float m[16]) + sgl_load_transpose_matrix(const float m[16]) + + ...directly multiply the current matrix from a float[16] array: + + sgl_mult_matrix(const float m[16]) + sgl_mult_transpose_matrix(const float m[16]) + + The memory layout of those float[16] arrays is the same as in OpenGL. + + ...more matrix functions: + + sgl_frustum(float left, float right, float bottom, float top, float near, float far) + sgl_ortho(float left, float right, float bottom, float top, float near, float far) + sgl_perspective(float fov_y, float aspect, float near, float far) + sgl_lookat(float eye_x, float eye_y, float eye_z, float center_x, float center_y, float center_z, float up_x, float up_y, float up_z) + + These functions work the same as glFrustum(), glOrtho(), gluPerspective() + and gluLookAt(). + + ...and finally to push / pop the current matrix stack: + + sgl_push_matrix(void) + sgl_pop_matrix(void) + + Again, these work the same as glPushMatrix() and glPopMatrix(). + + --- perform primitive rendering: + + ...set the current texture coordinate and color 'registers' with or + point size with: + + sgl_t2f(float u, float v) - set current texture coordinate + sgl_c*(...) - set current color + sgl_point_size(float size) - set current point size + + There are several functions for setting the color (as float values, + unsigned byte values, packed as unsigned 32-bit integer, with + and without alpha). + + NOTE that these are the only functions that can be called both inside + sgl_begin_*() / sgl_end() and outside. + + Also NOTE that point size is currently hardwired to 1.0f if the D3D11 + backend is used. + + ...start a primitive vertex sequence with: + + sgl_begin_points() + sgl_begin_lines() + sgl_begin_line_strip() + sgl_begin_triangles() + sgl_begin_triangle_strip() + sgl_begin_quads() + + ...after sgl_begin_*() specify vertices: + + sgl_v*(...) + sgl_v*_t*(...) + sgl_v*_c*(...) + sgl_v*_t*_c*(...) + + These functions write a new vertex to sokol-gl's internal vertex buffer, + optionally with texture-coords and color. If the texture coordinate + and/or color is missing, it will be taken from the current texture-coord + and color 'register'. + + ...finally, after specifying vertices, call: + + sgl_end() + + This will record a new draw command in sokol-gl's internal command + list, or it will extend the previous draw command if no relevant + state has changed since the last sgl_begin/end pair. + + --- inside a sokol-gfx rendering pass, call the sgl_draw() function + to render the currently active context: + + sgl_draw() + + ...or alternatively call: + + sgl_context_draw(ctx) + + ...to render an explicitly provided context. + + This will render everything that has been recorded in the context since + the last call to sgl_draw() through sokol-gfx, and will 'rewind' the internal + vertex-, uniform- and command-buffers. + + --- each sokol-gl context tracks internal error states which can + be obtains via: + + sgl_error_t sgl_error() + + ...alternatively with an explicit context argument: + + sgl_error_t sgl_context_error(ctx); + + ...this returns a struct with the following booleans: + + .any - true if any of the below errors is true + .vertices_full - internal vertex buffer is full (checked in sgl_end()) + .uniforms_full - the internal uniforms buffer is full (checked in sgl_end()) + .commands_full - the internal command buffer is full (checked in sgl_end()) + .stack_overflow - matrix- or pipeline-stack overflow + .stack_underflow - matrix- or pipeline-stack underflow + .no_context - the active context no longer exists + + ...depending on the above error state, sgl_draw() may skip rendering + completely, or only draw partial geometry + + --- you can get the number of recorded vertices and draw commands in the current + frame and active sokol-gl context via: + + int sgl_num_vertices() + int sgl_num_commands() + + ...this allows you to check whether the vertex or command pools are running + full before the overflow actually happens (in this case you could also + check the error booleans in the result of sgl_error()). + + RENDER LAYERS + ============= + Render layers allow to split sokol-gl rendering into separate draw-command + groups which can then be rendered separately in a sokol-gfx draw pass. This + allows to mix/interleave sokol-gl rendering with other render operations. + + Layered rendering is controlled through two functions: + + sgl_layer(int layer_id) + sgl_draw_layer(int layer_id) + + (and the context-variant sgl_draw_layer(): sgl_context_draw_layer() + + The sgl_layer() function sets the 'current layer', any sokol-gl calls + which internally record draw commands will also store the current layer + in the draw command, and later in a sokol-gfx render pass, a call + to sgl_draw_layer() will only render the draw commands that have + a matching layer. + + The default layer is '0', this is active after sokol-gl setup, and + is also restored at the start of a new frame (but *not* by calling + sgl_defaults()). + + NOTE that calling sgl_draw() is equivalent with sgl_draw_layer(0) + (in general you should either use either use sgl_draw() or + sgl_draw_layer() in an application, but not both). + + WORKING WITH CONTEXTS: + ====================== + If you want to render to more than one sokol-gfx render pass you need to + work with additional sokol-gl context objects (one context object for + each offscreen rendering pass, in addition to the implicitly created + 'default context'. + + All sokol-gl state is tracked per context, and there is always a "current + context" (with the notable exception that the currently set context is + destroyed, more on that later). + + Using multiple contexts can also be useful if you only render in + a single pass, but want to maintain multiple independent "state buckets". + + To create new context object, call: + + sgl_context ctx = sgl_make_context(&(sgl_context_desc){ + .max_vertices = ..., // default: 64k + .max_commands = ..., // default: 16k + .color_format = ..., + .depth_format = ..., + .sample_count = ..., + }); + + The color_format, depth_format and sample_count items must be compatible + with the render pass the sgl_draw() or sgL_context_draw() function + will be called in. + + Creating a context does *not* make the context current. To do this, call: + + sgl_set_context(ctx); + + The currently active context will implicitly be used by most sokol-gl functions + which don't take an explicit context handle as argument. + + To switch back to the default context, pass the global constant SGL_DEFAULT_CONTEXT: + + sgl_set_context(SGL_DEFAULT_CONTEXT); + + ...or alternatively use the function sgl_default_context() instead of the + global constant: + + sgl_set_context(sgl_default_context()); + + To get the currently active context, call: + + sgl_context cur_ctx = sgl_get_context(); + + The following functions exist in two variants, one which use the currently + active context (set with sgl_set_context()), and another version which + takes an explicit context handle instead: + + sgl_make_pipeline() vs sgl_context_make_pipeline() + sgl_error() vs sgl_context_error(); + sgl_draw() vs sgl_context_draw(); + + Except for using the currently active context versus a provided context + handle, the two variants are exactlyidentical, e.g. the following + code sequences do the same thing: + + sgl_set_context(ctx); + sgl_pipeline pip = sgl_make_pipeline(...); + sgl_error_t err = sgl_error(); + sgl_draw(); + + vs + + sgl_pipeline pip = sgl_context_make_pipeline(ctx, ...); + sgl_error_t err = sgl_context_error(ctx); + sgl_context_draw(ctx); + + Destroying the currently active context is a 'soft error'. All following + calls which require a currently active context will silently fail, + and sgl_error() will return SGL_ERROR_NO_CONTEXT. + + UNDER THE HOOD: + =============== + sokol_gl.h works by recording vertex data and rendering commands into + memory buffers, and then drawing the recorded commands via sokol_gfx.h + + The only functions which call into sokol_gfx.h are: + - sgl_setup() + - sgl_shutdown() + - sgl_draw() (and variants) + + sgl_setup() must be called after initializing sokol-gfx. + sgl_shutdown() must be called before shutting down sokol-gfx. + sgl_draw() must be called once per frame inside a sokol-gfx render pass. + + All other sokol-gl function can be called anywhere in a frame, since + they just record data into memory buffers owned by sokol-gl. + + What happens in: + + sgl_setup(): + Unique resources shared by all contexts are created: + - a shader object (using embedded shader source or byte code) + - an 8x8 white default texture + The default context is created, which involves: + - 3 memory buffers are created, one for vertex data, + one for uniform data, and one for commands + - a dynamic vertex buffer is created + - the default sgl_pipeline object is created, which involves + creating 5 sg_pipeline objects + + One vertex is 24 bytes: + - float3 position + - float2 texture coords + - uint32_t color + + One uniform block is 128 bytes: + - mat4 model-view-projection matrix + - mat4 texture matrix + + One draw command is ca. 24 bytes for the actual + command code plus command arguments. + + Each sgl_end() consumes one command, and one uniform block + (only when the matrices have changed). + The required size for one sgl_begin/end pair is (at most): + + (152 + 24 * num_verts) bytes + + sgl_shutdown(): + - all sokol-gfx resources (buffer, shader, default-texture and + all pipeline objects) are destroyed + - the 3 memory buffers are freed + + sgl_draw() (and variants) + - copy all recorded vertex data into the dynamic sokol-gfx buffer + via a call to sg_update_buffer() + - for each recorded command: + - if the layer number stored in the command doesn't match + the layer that's to be rendered, skip to the next + command + - if it's a viewport command, call sg_apply_viewport() + - if it's a scissor-rect command, call sg_apply_scissor_rect() + - if it's a draw command: + - depending on what has changed since the last draw command, + call sg_apply_pipeline(), sg_apply_bindings() and + sg_apply_uniforms() + - finally call sg_draw() + + All other functions only modify the internally tracked state, add + data to the vertex, uniform and command buffers, or manipulate + the matrix stack. + + ON DRAW COMMAND MERGING + ======================= + Not every call to sgl_end() will automatically record a new draw command. + If possible, the previous draw command will simply be extended, + resulting in fewer actual draw calls later in sgl_draw(). + + A draw command will be merged with the previous command if "no relevant + state has changed" since the last sgl_end(), meaning: + + - no calls to sgl_viewport() and sgl_scissor_rect() + - the primitive type hasn't changed + - the primitive type isn't a 'strip type' (no line or triangle strip) + - the pipeline state object hasn't changed + - the current layer hasn't changed + - none of the matrices has changed + - none of the texture state has changed + + Merging a draw command simply means that the number of vertices + to render in the previous draw command will be incremented by the + number of vertices in the new draw command. + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sgl_setup(&(sgl_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sgl_setup(&(sgl_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sgl' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SGL_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_gl.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-gl like this: + + sgl_setup(&(sgl_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ +#import,dir "../gfx"(DEBUG = USE_DLL, USE_GL = USE_DLL, USE_DLL = USE_DLL); + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_gl_clib :: #library "sokol_gl_windows_x64_gl_debug"; } + else { sokol_gl_clib :: #library "sokol_gl_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_gl_clib :: #library "sokol_gl_windows_x64_d3d11_debug"; } + else { sokol_gl_clib :: #library "sokol_gl_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_windows_x64_gl_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_windows_x64_d3d11_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_gl_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_arm64_gl_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_x64_gl_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_arm64_metal_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_x64_metal_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_linux_x64_gl_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_gl_clib :: #library,no_dll "sokol_gl_wasm_gl_debug"; } + else { sokol_gl_clib :: #library,no_dll "sokol_gl_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// setup/shutdown/misc +sgl_setup :: (desc: *sgl_desc_t) -> void #foreign sokol_gl_clib; +sgl_shutdown :: () -> void #foreign sokol_gl_clib; +sgl_rad :: (deg: float) -> float #foreign sokol_gl_clib; +sgl_deg :: (rad: float) -> float #foreign sokol_gl_clib; +sgl_error :: () -> sgl_error_t #foreign sokol_gl_clib; +sgl_context_error :: (ctx: sgl_context) -> sgl_error_t #foreign sokol_gl_clib; +// context functions +sgl_make_context :: (desc: *sgl_context_desc_t) -> sgl_context #foreign sokol_gl_clib; +sgl_destroy_context :: (ctx: sgl_context) -> void #foreign sokol_gl_clib; +sgl_set_context :: (ctx: sgl_context) -> void #foreign sokol_gl_clib; +sgl_get_context :: () -> sgl_context #foreign sokol_gl_clib; +sgl_default_context :: () -> sgl_context #foreign sokol_gl_clib; +// get information about recorded vertices and commands in current context +sgl_num_vertices :: () -> s32 #foreign sokol_gl_clib; +sgl_num_commands :: () -> s32 #foreign sokol_gl_clib; +// draw recorded commands (call inside a sokol-gfx render pass) +sgl_draw :: () -> void #foreign sokol_gl_clib; +sgl_context_draw :: (ctx: sgl_context) -> void #foreign sokol_gl_clib; +sgl_draw_layer :: (layer_id: s32) -> void #foreign sokol_gl_clib; +sgl_context_draw_layer :: (ctx: sgl_context, layer_id: s32) -> void #foreign sokol_gl_clib; +// create and destroy pipeline objects +sgl_make_pipeline :: (desc: *sg_pipeline_desc) -> sgl_pipeline #foreign sokol_gl_clib; +sgl_context_make_pipeline :: (ctx: sgl_context, desc: *sg_pipeline_desc) -> sgl_pipeline #foreign sokol_gl_clib; +sgl_destroy_pipeline :: (pip: sgl_pipeline) -> void #foreign sokol_gl_clib; +// render state functions +sgl_defaults :: () -> void #foreign sokol_gl_clib; +sgl_viewport :: (x: s32, y: s32, w: s32, h: s32, origin_top_left: bool) -> void #foreign sokol_gl_clib; +sgl_viewportf :: (x: float, y: float, w: float, h: float, origin_top_left: bool) -> void #foreign sokol_gl_clib; +sgl_scissor_rect :: (x: s32, y: s32, w: s32, h: s32, origin_top_left: bool) -> void #foreign sokol_gl_clib; +sgl_scissor_rectf :: (x: float, y: float, w: float, h: float, origin_top_left: bool) -> void #foreign sokol_gl_clib; +sgl_enable_texture :: () -> void #foreign sokol_gl_clib; +sgl_disable_texture :: () -> void #foreign sokol_gl_clib; +sgl_texture :: (img: sg_image, smp: sg_sampler) -> void #foreign sokol_gl_clib; +sgl_layer :: (layer_id: s32) -> void #foreign sokol_gl_clib; +// pipeline stack functions +sgl_load_default_pipeline :: () -> void #foreign sokol_gl_clib; +sgl_load_pipeline :: (pip: sgl_pipeline) -> void #foreign sokol_gl_clib; +sgl_push_pipeline :: () -> void #foreign sokol_gl_clib; +sgl_pop_pipeline :: () -> void #foreign sokol_gl_clib; +// matrix stack functions +sgl_matrix_mode_modelview :: () -> void #foreign sokol_gl_clib; +sgl_matrix_mode_projection :: () -> void #foreign sokol_gl_clib; +sgl_matrix_mode_texture :: () -> void #foreign sokol_gl_clib; +sgl_load_identity :: () -> void #foreign sokol_gl_clib; +sgl_load_matrix :: (m: *float) -> void #foreign sokol_gl_clib; +sgl_load_transpose_matrix :: (m: *float) -> void #foreign sokol_gl_clib; +sgl_mult_matrix :: (m: *float) -> void #foreign sokol_gl_clib; +sgl_mult_transpose_matrix :: (m: *float) -> void #foreign sokol_gl_clib; +sgl_rotate :: (angle_rad: float, x: float, y: float, z: float) -> void #foreign sokol_gl_clib; +sgl_scale :: (x: float, y: float, z: float) -> void #foreign sokol_gl_clib; +sgl_translate :: (x: float, y: float, z: float) -> void #foreign sokol_gl_clib; +sgl_frustum :: (l: float, r: float, b: float, t: float, n: float, f: float) -> void #foreign sokol_gl_clib; +sgl_ortho :: (l: float, r: float, b: float, t: float, n: float, f: float) -> void #foreign sokol_gl_clib; +sgl_perspective :: (fov_y: float, aspect: float, z_near: float, z_far: float) -> void #foreign sokol_gl_clib; +sgl_lookat :: (eye_x: float, eye_y: float, eye_z: float, center_x: float, center_y: float, center_z: float, up_x: float, up_y: float, up_z: float) -> void #foreign sokol_gl_clib; +sgl_push_matrix :: () -> void #foreign sokol_gl_clib; +sgl_pop_matrix :: () -> void #foreign sokol_gl_clib; +// these functions only set the internal 'current texcoord / color / point size' (valid inside or outside begin/end) +sgl_t2f :: (u: float, v: float) -> void #foreign sokol_gl_clib; +sgl_c3f :: (r: float, g: float, b: float) -> void #foreign sokol_gl_clib; +sgl_c4f :: (r: float, g: float, b: float, a: float) -> void #foreign sokol_gl_clib; +sgl_c3b :: (r: u8, g: u8, b: u8) -> void #foreign sokol_gl_clib; +sgl_c4b :: (r: u8, g: u8, b: u8, a: u8) -> void #foreign sokol_gl_clib; +sgl_c1i :: (rgba: u32) -> void #foreign sokol_gl_clib; +sgl_point_size :: (s: float) -> void #foreign sokol_gl_clib; +// define primitives, each begin/end is one draw command +sgl_begin_points :: () -> void #foreign sokol_gl_clib; +sgl_begin_lines :: () -> void #foreign sokol_gl_clib; +sgl_begin_line_strip :: () -> void #foreign sokol_gl_clib; +sgl_begin_triangles :: () -> void #foreign sokol_gl_clib; +sgl_begin_triangle_strip :: () -> void #foreign sokol_gl_clib; +sgl_begin_quads :: () -> void #foreign sokol_gl_clib; +sgl_v2f :: (x: float, y: float) -> void #foreign sokol_gl_clib; +sgl_v3f :: (x: float, y: float, z: float) -> void #foreign sokol_gl_clib; +sgl_v2f_t2f :: (x: float, y: float, u: float, v: float) -> void #foreign sokol_gl_clib; +sgl_v3f_t2f :: (x: float, y: float, z: float, u: float, v: float) -> void #foreign sokol_gl_clib; +sgl_v2f_c3f :: (x: float, y: float, r: float, g: float, b: float) -> void #foreign sokol_gl_clib; +sgl_v2f_c3b :: (x: float, y: float, r: u8, g: u8, b: u8) -> void #foreign sokol_gl_clib; +sgl_v2f_c4f :: (x: float, y: float, r: float, g: float, b: float, a: float) -> void #foreign sokol_gl_clib; +sgl_v2f_c4b :: (x: float, y: float, r: u8, g: u8, b: u8, a: u8) -> void #foreign sokol_gl_clib; +sgl_v2f_c1i :: (x: float, y: float, rgba: u32) -> void #foreign sokol_gl_clib; +sgl_v3f_c3f :: (x: float, y: float, z: float, r: float, g: float, b: float) -> void #foreign sokol_gl_clib; +sgl_v3f_c3b :: (x: float, y: float, z: float, r: u8, g: u8, b: u8) -> void #foreign sokol_gl_clib; +sgl_v3f_c4f :: (x: float, y: float, z: float, r: float, g: float, b: float, a: float) -> void #foreign sokol_gl_clib; +sgl_v3f_c4b :: (x: float, y: float, z: float, r: u8, g: u8, b: u8, a: u8) -> void #foreign sokol_gl_clib; +sgl_v3f_c1i :: (x: float, y: float, z: float, rgba: u32) -> void #foreign sokol_gl_clib; +sgl_v2f_t2f_c3f :: (x: float, y: float, u: float, v: float, r: float, g: float, b: float) -> void #foreign sokol_gl_clib; +sgl_v2f_t2f_c3b :: (x: float, y: float, u: float, v: float, r: u8, g: u8, b: u8) -> void #foreign sokol_gl_clib; +sgl_v2f_t2f_c4f :: (x: float, y: float, u: float, v: float, r: float, g: float, b: float, a: float) -> void #foreign sokol_gl_clib; +sgl_v2f_t2f_c4b :: (x: float, y: float, u: float, v: float, r: u8, g: u8, b: u8, a: u8) -> void #foreign sokol_gl_clib; +sgl_v2f_t2f_c1i :: (x: float, y: float, u: float, v: float, rgba: u32) -> void #foreign sokol_gl_clib; +sgl_v3f_t2f_c3f :: (x: float, y: float, z: float, u: float, v: float, r: float, g: float, b: float) -> void #foreign sokol_gl_clib; +sgl_v3f_t2f_c3b :: (x: float, y: float, z: float, u: float, v: float, r: u8, g: u8, b: u8) -> void #foreign sokol_gl_clib; +sgl_v3f_t2f_c4f :: (x: float, y: float, z: float, u: float, v: float, r: float, g: float, b: float, a: float) -> void #foreign sokol_gl_clib; +sgl_v3f_t2f_c4b :: (x: float, y: float, z: float, u: float, v: float, r: u8, g: u8, b: u8, a: u8) -> void #foreign sokol_gl_clib; +sgl_v3f_t2f_c1i :: (x: float, y: float, z: float, u: float, v: float, rgba: u32) -> void #foreign sokol_gl_clib; +sgl_end :: () -> void #foreign sokol_gl_clib; + +sgl_log_item_t :: enum u32 { + OK; + MALLOC_FAILED; + MAKE_PIPELINE_FAILED; + PIPELINE_POOL_EXHAUSTED; + ADD_COMMIT_LISTENER_FAILED; + CONTEXT_POOL_EXHAUSTED; + CANNOT_DESTROY_DEFAULT_CONTEXT; +} + +sgl_logger_t :: struct { + func : (a0: *u8, a1: u32, a2: u32, a3: *u8, a4: u32, a5: *u8, a6: *void) #c_call; + user_data : *void; +} + +sgl_pipeline :: struct { + id : u32; +} + +sgl_context :: struct { + id : u32; +} + +sgl_error_t :: struct { + any : bool; + vertices_full : bool; + uniforms_full : bool; + commands_full : bool; + stack_overflow : bool; + stack_underflow : bool; + no_context : bool; +} + +sgl_context_desc_t :: struct { + max_vertices : s32; + max_commands : s32; + color_format : sg_pixel_format; + depth_format : sg_pixel_format; + sample_count : s32; +} + +sgl_allocator_t :: struct { + alloc_fn : (a0: u64, a1: *void) -> *void #c_call; + free_fn : (a0: *void, a1: *void) #c_call; + user_data : *void; +} + +sgl_desc_t :: struct { + max_vertices : s32; + max_commands : s32; + context_pool_size : s32; + pipeline_pool_size : s32; + color_format : sg_pixel_format; + depth_format : sg_pixel_format; + sample_count : s32; + face_winding : sg_face_winding; + allocator : sgl_allocator_t; + logger : sgl_logger_t; +} + diff --git a/modules/sokol-jai/sokol/glue/module.jai b/modules/sokol-jai/sokol/glue/module.jai new file mode 100644 index 0000000..77f6130 --- /dev/null +++ b/modules/sokol-jai/sokol/glue/module.jai @@ -0,0 +1,155 @@ +// machine generated, do not edit + +/* + + sokol_glue.h -- glue helper functions for sokol headers + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GLUE_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_GLUE_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GLUE_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_glue.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_GLUE_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + OVERVIEW + ======== + sokol_glue.h provides glue helper functions between sokol_gfx.h and sokol_app.h, + so that sokol_gfx.h doesn't need to depend on sokol_app.h but can be + used with different window system glue libraries. + + PROVIDED FUNCTIONS + ================== + + sg_environment sglue_environment(void) + + Returns an sg_environment struct initialized by calling sokol_app.h + functions. Use this in the sg_setup() call like this: + + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + ... + }); + + sg_swapchain sglue_swapchain(void) + + Returns an sg_swapchain struct initialized by calling sokol_app.h + functions. Use this in sg_begin_pass() for a 'swapchain pass' like + this: + + sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain(), ... }); + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ +#import,dir "../gfx"(DEBUG = USE_DLL, USE_GL = USE_DLL, USE_DLL = USE_DLL); + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_glue_clib :: #library "sokol_glue_windows_x64_gl_debug"; } + else { sokol_glue_clib :: #library "sokol_glue_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_glue_clib :: #library "sokol_glue_windows_x64_d3d11_debug"; } + else { sokol_glue_clib :: #library "sokol_glue_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_windows_x64_gl_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_windows_x64_d3d11_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_glue_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_arm64_gl_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_x64_gl_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_arm64_metal_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_x64_metal_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_linux_x64_gl_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_glue_clib :: #library,no_dll "sokol_glue_wasm_gl_debug"; } + else { sokol_glue_clib :: #library,no_dll "sokol_glue_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +sglue_environment :: () -> sg_environment #foreign sokol_glue_clib; +sglue_swapchain :: () -> sg_swapchain #foreign sokol_glue_clib; + diff --git a/modules/sokol-jai/sokol/log/module.jai b/modules/sokol-jai/sokol/log/module.jai new file mode 100644 index 0000000..46d63b3 --- /dev/null +++ b/modules/sokol-jai/sokol/log/module.jai @@ -0,0 +1,184 @@ +// machine generated, do not edit + +/* + + sokol_log.h -- common logging callback for sokol headers + + Project URL: https://github.com/floooh/sokol + + Example code: https://github.com/floooh/sokol-samples + + Do this: + #define SOKOL_IMPL or + #define SOKOL_LOG_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines when building the implementation: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_LOG_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + Optionally define the following for verbose output: + + SOKOL_DEBUG - by default this is defined if _DEBUG is defined + + + OVERVIEW + ======== + sokol_log.h provides a default logging callback for other sokol headers. + + To use the default log callback, just include sokol_log.h and provide + a function pointer to the 'slog_func' function when setting up the + sokol library: + + For instance with sokol_audio.h: + + #include "sokol_log.h" + ... + saudio_setup(&(saudio_desc){ .logger.func = slog_func }); + + Logging output goes to stderr and/or a platform specific logging subsystem + (which means that in some scenarios you might see logging messages duplicated): + + - Windows: stderr + OutputDebugStringA() + - macOS/iOS/Linux: stderr + syslog() + - Emscripten: console.info()/warn()/error() + - Android: __android_log_write() + + On Windows with sokol_app.h also note the runtime config items to make + stdout/stderr output visible on the console for WinMain() applications + via sapp_desc.win32_console_attach or sapp_desc.win32_console_create, + however when running in a debugger on Windows, the logging output should + show up on the debug output UI panel. + + In debug mode, a log message might look like this: + + [sspine][error][id:12] /Users/floh/projects/sokol/util/sokol_spine.h:3472:0: + SKELETON_DESC_NO_ATLAS: no atlas object provided in sspine_skeleton_desc.atlas + + The source path and line number is formatted like compiler errors, in some IDEs (like VSCode) + such error messages are clickable. + + In release mode, logging is less verbose as to not bloat the executable with string data, but you still get + enough information to identify the type and location of an error: + + [sspine][error][id:12][line:3472] + + RULES FOR WRITING YOUR OWN LOGGING FUNCTION + =========================================== + - must be re-entrant because it might be called from different threads + - must treat **all** provided string pointers as optional (can be null) + - don't store the string pointers, copy the string data instead + - must not return for log level panic + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2023 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_log_clib :: #library "sokol_log_windows_x64_gl_debug"; } + else { sokol_log_clib :: #library "sokol_log_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_log_clib :: #library "sokol_log_windows_x64_d3d11_debug"; } + else { sokol_log_clib :: #library "sokol_log_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_windows_x64_gl_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_windows_x64_d3d11_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_log_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_macos_arm64_gl_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_macos_x64_gl_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_macos_arm64_metal_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_macos_x64_metal_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_linux_x64_gl_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_log_clib :: #library,no_dll "sokol_log_wasm_gl_debug"; } + else { sokol_log_clib :: #library,no_dll "sokol_log_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +/* + Plug this function into the 'logger.func' struct item when initializing any of the sokol + headers. For instance for sokol_audio.h it would look like this: + + saudio_setup(&(saudio_desc){ + .logger = { + .func = slog_func + } + }); +*/ +slog_func :: (tag: *u8, log_level: u32, log_item: u32, message: *u8, line_nr: u32, filename: *u8, user_data: *void) -> void #foreign sokol_log_clib; + diff --git a/modules/sokol-jai/sokol/shape/module.jai b/modules/sokol-jai/sokol/shape/module.jai new file mode 100644 index 0000000..e268eb6 --- /dev/null +++ b/modules/sokol-jai/sokol/shape/module.jai @@ -0,0 +1,560 @@ +// machine generated, do not edit + +/* + + sokol_shape.h -- create simple primitive shapes for sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_SHAPE_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Include the following headers before including sokol_shape.h: + + sokol_gfx.h + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_SHAPE_API_DECL- public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_SHAPE_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_shape.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_SHAPE_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + FEATURE OVERVIEW + ================ + sokol_shape.h creates vertices and indices for simple shapes and + builds structs which can be plugged into sokol-gfx resource + creation functions: + + The following shape types are supported: + + - plane + - cube + - sphere (with poles, not geodesic) + - cylinder + - torus (donut) + + Generated vertices look like this: + + typedef struct sshape_vertex_t { + float x, y, z; + uint32_t normal; // packed normal as BYTE4N + uint16_t u, v; // packed uv coords as USHORT2N + uint32_t color; // packed color as UBYTE4N (r,g,b,a); + } sshape_vertex_t; + + Indices are generally 16-bits wide (SG_INDEXTYPE_UINT16) and the indices + are written as triangle-lists (SG_PRIMITIVETYPE_TRIANGLES). + + EXAMPLES: + ========= + + Create multiple shapes into the same vertex- and index-buffer and + render with separate draw calls: + + https://github.com/floooh/sokol-samples/blob/master/sapp/shapes-sapp.c + + Same as the above, but pre-transform shapes and merge them into a single + shape that's rendered with a single draw call. + + https://github.com/floooh/sokol-samples/blob/master/sapp/shapes-transform-sapp.c + + STEP-BY-STEP: + ============= + + Setup an sshape_buffer_t struct with pointers to memory buffers where + generated vertices and indices will be written to: + + ```c + sshape_vertex_t vertices[512]; + uint16_t indices[4096]; + + sshape_buffer_t buf = { + .vertices = { + .buffer = SSHAPE_RANGE(vertices), + }, + .indices = { + .buffer = SSHAPE_RANGE(indices), + } + }; + ``` + + To find out how big those memory buffers must be (in case you want + to allocate dynamically) call the following functions: + + ```c + sshape_sizes_t sshape_plane_sizes(uint32_t tiles); + sshape_sizes_t sshape_box_sizes(uint32_t tiles); + sshape_sizes_t sshape_sphere_sizes(uint32_t slices, uint32_t stacks); + sshape_sizes_t sshape_cylinder_sizes(uint32_t slices, uint32_t stacks); + sshape_sizes_t sshape_torus_sizes(uint32_t sides, uint32_t rings); + ``` + + The returned sshape_sizes_t struct contains vertex- and index-counts + as well as the equivalent buffer sizes in bytes. For instance: + + ```c + sshape_sizes_t sizes = sshape_sphere_sizes(36, 12); + uint32_t num_vertices = sizes.vertices.num; + uint32_t num_indices = sizes.indices.num; + uint32_t vertex_buffer_size = sizes.vertices.size; + uint32_t index_buffer_size = sizes.indices.size; + ``` + + With the sshape_buffer_t struct that was setup earlier, call any + of the shape-builder functions: + + ```c + sshape_buffer_t sshape_build_plane(const sshape_buffer_t* buf, const sshape_plane_t* params); + sshape_buffer_t sshape_build_box(const sshape_buffer_t* buf, const sshape_box_t* params); + sshape_buffer_t sshape_build_sphere(const sshape_buffer_t* buf, const sshape_sphere_t* params); + sshape_buffer_t sshape_build_cylinder(const sshape_buffer_t* buf, const sshape_cylinder_t* params); + sshape_buffer_t sshape_build_torus(const sshape_buffer_t* buf, const sshape_torus_t* params); + ``` + + Note how the sshape_buffer_t struct is both an input value and the + return value. This can be used to append multiple shapes into the + same vertex- and index-buffers (more on this later). + + The second argument is a struct which holds creation parameters. + + For instance to build a sphere with radius 2, 36 "cake slices" and 12 stacks: + + ```c + sshape_buffer_t buf = ...; + buf = sshape_build_sphere(&buf, &(sshape_sphere_t){ + .radius = 2.0f, + .slices = 36, + .stacks = 12, + }); + ``` + + If the provided buffers are big enough to hold all generated vertices and + indices, the "valid" field in the result will be true: + + ```c + assert(buf.valid); + ``` + + The shape creation parameters have "useful defaults", refer to the + actual C struct declarations below to look up those defaults. + + You can also provide additional creation parameters, like a common vertex + color, a debug-helper to randomize colors, tell the shape builder function + to merge the new shape with the previous shape into the same draw-element-range, + or a 4x4 transform matrix to move, rotate and scale the generated vertices: + + ```c + sshape_buffer_t buf = ...; + buf = sshape_build_sphere(&buf, &(sshape_sphere_t){ + .radius = 2.0f, + .slices = 36, + .stacks = 12, + // merge with previous shape into a single element-range + .merge = true, + // set vertex color to red+opaque + .color = sshape_color_4f(1.0f, 0.0f, 0.0f, 1.0f), + // set position to y = 2.0 + .transform = { + .m = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 2.0f, 0.0f, 1.0f }, + } + } + }); + assert(buf.valid); + ``` + + The following helper functions can be used to build a packed + color value or to convert from external matrix types: + + ```c + uint32_t sshape_color_4f(float r, float g, float b, float a); + uint32_t sshape_color_3f(float r, float g, float b); + uint32_t sshape_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + uint32_t sshape_color_3b(uint8_t r, uint8_t g, uint8_t b); + sshape_mat4_t sshape_mat4(const float m[16]); + sshape_mat4_t sshape_mat4_transpose(const float m[16]); + ``` + + After the shape builder function has been called, the following functions + are used to extract the build result for plugging into sokol_gfx.h: + + ```c + sshape_element_range_t sshape_element_range(const sshape_buffer_t* buf); + sg_buffer_desc sshape_vertex_buffer_desc(const sshape_buffer_t* buf); + sg_buffer_desc sshape_index_buffer_desc(const sshape_buffer_t* buf); + sg_vertex_buffer_layout_state sshape_vertex_buffer_layout_state(void); + sg_vertex_attr_state sshape_position_vertex_attr_state(void); + sg_vertex_attr_state sshape_normal_vertex_attr_state(void); + sg_vertex_attr_state sshape_texcoord_vertex_attr_state(void); + sg_vertex_attr_state sshape_color_vertex_attr_state(void); + ``` + + The sshape_element_range_t struct contains the base-index and number of + indices which can be plugged into the sg_draw() call: + + ```c + sshape_element_range_t elms = sshape_element_range(&buf); + ... + sg_draw(elms.base_element, elms.num_elements, 1); + ``` + + To create sokol-gfx vertex- and index-buffers from the generated + shape data: + + ```c + // create sokol-gfx vertex buffer + sg_buffer_desc vbuf_desc = sshape_vertex_buffer_desc(&buf); + sg_buffer vbuf = sg_make_buffer(&vbuf_desc); + + // create sokol-gfx index buffer + sg_buffer_desc ibuf_desc = sshape_index_buffer_desc(&buf); + sg_buffer ibuf = sg_make_buffer(&ibuf_desc); + ``` + + The remaining functions are used to populate the vertex-layout item + in sg_pipeline_desc, note that these functions don't depend on the + created geometry, they always return the same result: + + ```c + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .buffers[0] = sshape_vertex_buffer_layout_state(), + .attrs = { + [0] = sshape_position_vertex_attr_state(), + [1] = ssape_normal_vertex_attr_state(), + [2] = sshape_texcoord_vertex_attr_state(), + [3] = sshape_color_vertex_attr_state() + } + }, + ... + }); + ``` + + Note that you don't have to use all generated vertex attributes in the + pipeline's vertex layout, the sg_vertex_buffer_layout_state struct returned + by sshape_vertex_buffer_layout_state() contains the correct vertex stride + to skip vertex components. + + WRITING MULTIPLE SHAPES INTO THE SAME BUFFER + ============================================ + You can merge multiple shapes into the same vertex- and + index-buffers and either render them as a single shape, or + in separate draw calls. + + To build a single shape made of two cubes which can be rendered + in a single draw-call: + + ``` + sshape_vertex_t vertices[128]; + uint16_t indices[16]; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vertices), + .indices.buffer = SSHAPE_RANGE(indices) + }; + + // first cube at pos x=-2.0 (with default size of 1x1x1) + buf = sshape_build_cube(&buf, &(sshape_box_t){ + .transform = { + .m = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + {-2.0f, 0.0f, 0.0f, 1.0f }, + } + } + }); + // ...and append another cube at pos pos=+1.0 + // NOTE the .merge = true, this tells the shape builder + // function to not advance the current shape start offset + buf = sshape_build_cube(&buf, &(sshape_box_t){ + .merge = true, + .transform = { + .m = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + {-2.0f, 0.0f, 0.0f, 1.0f }, + } + } + }); + assert(buf.valid); + + // skipping buffer- and pipeline-creation... + + sshape_element_range_t elms = sshape_element_range(&buf); + sg_draw(elms.base_element, elms.num_elements, 1); + ``` + + To render the two cubes in separate draw-calls, the element-ranges used + in the sg_draw() calls must be captured right after calling the + builder-functions: + + ```c + sshape_vertex_t vertices[128]; + uint16_t indices[16]; + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vertices), + .indices.buffer = SSHAPE_RANGE(indices) + }; + + // build a red cube... + buf = sshape_build_cube(&buf, &(sshape_box_t){ + .color = sshape_color_3b(255, 0, 0) + }); + sshape_element_range_t red_cube = sshape_element_range(&buf); + + // append a green cube to the same vertex-/index-buffer: + buf = sshape_build_cube(&bud, &sshape_box_t){ + .color = sshape_color_3b(0, 255, 0); + }); + sshape_element_range_t green_cube = sshape_element_range(&buf); + + // skipping buffer- and pipeline-creation... + + sg_draw(red_cube.base_element, red_cube.num_elements, 1); + sg_draw(green_cube.base_element, green_cube.num_elements, 1); + ``` + + ...that's about all :) + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2020 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ +#import,dir "../gfx"(DEBUG = USE_DLL, USE_GL = USE_DLL, USE_DLL = USE_DLL); + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_shape_clib :: #library "sokol_shape_windows_x64_gl_debug"; } + else { sokol_shape_clib :: #library "sokol_shape_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_shape_clib :: #library "sokol_shape_windows_x64_d3d11_debug"; } + else { sokol_shape_clib :: #library "sokol_shape_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_windows_x64_gl_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_windows_x64_d3d11_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_shape_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_arm64_gl_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_x64_gl_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_arm64_metal_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_x64_metal_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_linux_x64_gl_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_shape_clib :: #library,no_dll "sokol_shape_wasm_gl_debug"; } + else { sokol_shape_clib :: #library,no_dll "sokol_shape_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +// shape builder functions +sshape_build_plane :: (buf: *sshape_buffer_t, params: *sshape_plane_t) -> sshape_buffer_t #foreign sokol_shape_clib; +sshape_build_box :: (buf: *sshape_buffer_t, params: *sshape_box_t) -> sshape_buffer_t #foreign sokol_shape_clib; +sshape_build_sphere :: (buf: *sshape_buffer_t, params: *sshape_sphere_t) -> sshape_buffer_t #foreign sokol_shape_clib; +sshape_build_cylinder :: (buf: *sshape_buffer_t, params: *sshape_cylinder_t) -> sshape_buffer_t #foreign sokol_shape_clib; +sshape_build_torus :: (buf: *sshape_buffer_t, params: *sshape_torus_t) -> sshape_buffer_t #foreign sokol_shape_clib; +// query required vertex- and index-buffer sizes in bytes +sshape_plane_sizes :: (tiles: u32) -> sshape_sizes_t #foreign sokol_shape_clib; +sshape_box_sizes :: (tiles: u32) -> sshape_sizes_t #foreign sokol_shape_clib; +sshape_sphere_sizes :: (slices: u32, stacks: u32) -> sshape_sizes_t #foreign sokol_shape_clib; +sshape_cylinder_sizes :: (slices: u32, stacks: u32) -> sshape_sizes_t #foreign sokol_shape_clib; +sshape_torus_sizes :: (sides: u32, rings: u32) -> sshape_sizes_t #foreign sokol_shape_clib; +// extract sokol-gfx desc structs and primitive ranges from build state +sshape_element_range :: (buf: *sshape_buffer_t) -> sshape_element_range_t #foreign sokol_shape_clib; +sshape_vertex_buffer_desc :: (buf: *sshape_buffer_t) -> sg_buffer_desc #foreign sokol_shape_clib; +sshape_index_buffer_desc :: (buf: *sshape_buffer_t) -> sg_buffer_desc #foreign sokol_shape_clib; +sshape_vertex_buffer_layout_state :: () -> sg_vertex_buffer_layout_state #foreign sokol_shape_clib; +sshape_position_vertex_attr_state :: () -> sg_vertex_attr_state #foreign sokol_shape_clib; +sshape_normal_vertex_attr_state :: () -> sg_vertex_attr_state #foreign sokol_shape_clib; +sshape_texcoord_vertex_attr_state :: () -> sg_vertex_attr_state #foreign sokol_shape_clib; +sshape_color_vertex_attr_state :: () -> sg_vertex_attr_state #foreign sokol_shape_clib; +// helper functions to build packed color value from floats or bytes +sshape_color_4f :: (r: float, g: float, b: float, a: float) -> u32 #foreign sokol_shape_clib; +sshape_color_3f :: (r: float, g: float, b: float) -> u32 #foreign sokol_shape_clib; +sshape_color_4b :: (r: u8, g: u8, b: u8, a: u8) -> u32 #foreign sokol_shape_clib; +sshape_color_3b :: (r: u8, g: u8, b: u8) -> u32 #foreign sokol_shape_clib; +// adapter function for filling matrix struct from generic float[16] array +sshape_mat4 :: (m: *float) -> sshape_mat4_t #foreign sokol_shape_clib; +sshape_mat4_transpose :: (m: *float) -> sshape_mat4_t #foreign sokol_shape_clib; + +sshape_range :: struct { + ptr : *void; + size : u64; +} + +sshape_mat4_t :: struct { + m : [4][4]float; +} + +sshape_vertex_t :: struct { + x : float; + y : float; + z : float; + normal : u32; + u : u16; + v : u16; + color : u32; +} + +sshape_element_range_t :: struct { + base_element : s32; + num_elements : s32; +} + +sshape_sizes_item_t :: struct { + num : u32; + size : u32; +} + +sshape_sizes_t :: struct { + vertices : sshape_sizes_item_t; + indices : sshape_sizes_item_t; +} + +sshape_buffer_item_t :: struct { + buffer : sshape_range; + data_size : u64; + shape_offset : u64; +} + +sshape_buffer_t :: struct { + valid : bool; + vertices : sshape_buffer_item_t; + indices : sshape_buffer_item_t; +} + +sshape_plane_t :: struct { + width : float; + depth : float; + tiles : u16; + color : u32; + random_colors : bool; + merge : bool; + transform : sshape_mat4_t; +} + +sshape_box_t :: struct { + width : float; + height : float; + depth : float; + tiles : u16; + color : u32; + random_colors : bool; + merge : bool; + transform : sshape_mat4_t; +} + +sshape_sphere_t :: struct { + radius : float; + slices : u16; + stacks : u16; + color : u32; + random_colors : bool; + merge : bool; + transform : sshape_mat4_t; +} + +sshape_cylinder_t :: struct { + radius : float; + height : float; + slices : u16; + stacks : u16; + color : u32; + random_colors : bool; + merge : bool; + transform : sshape_mat4_t; +} + +sshape_torus_t :: struct { + radius : float; + ring_radius : float; + sides : u16; + rings : u16; + color : u32; + random_colors : bool; + merge : bool; + transform : sshape_mat4_t; +} + diff --git a/modules/sokol-jai/sokol/time/module.jai b/modules/sokol-jai/sokol/time/module.jai new file mode 100644 index 0000000..ed6556f --- /dev/null +++ b/modules/sokol-jai/sokol/time/module.jai @@ -0,0 +1,187 @@ +// machine generated, do not edit + +/* + + sokol_time.h -- simple cross-platform time measurement + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_TIME_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_TIME_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_time.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + void stm_setup(); + Call once before any other functions to initialize sokol_time + (this calls for instance QueryPerformanceFrequency on Windows) + + uint64_t stm_now(); + Get current point in time in unspecified 'ticks'. The value that + is returned has no relation to the 'wall-clock' time and is + not in a specific time unit, it is only useful to compute + time differences. + + uint64_t stm_diff(uint64_t new, uint64_t old); + Computes the time difference between new and old. This will always + return a positive, non-zero value. + + uint64_t stm_since(uint64_t start); + Takes the current time, and returns the elapsed time since start + (this is a shortcut for "stm_diff(stm_now(), start)") + + uint64_t stm_laptime(uint64_t* last_time); + This is useful for measuring frame time and other recurring + events. It takes the current time, returns the time difference + to the value in last_time, and stores the current time in + last_time for the next call. If the value in last_time is 0, + the return value will be zero (this usually happens on the + very first call). + + uint64_t stm_round_to_common_refresh_rate(uint64_t duration) + This oddly named function takes a measured frame time and + returns the closest "nearby" common display refresh rate frame duration + in ticks. If the input duration isn't close to any common display + refresh rate, the input duration will be returned unchanged as a fallback. + The main purpose of this function is to remove jitter/inaccuracies from + measured frame times, and instead use the display refresh rate as + frame duration. + NOTE: for more robust frame timing, consider using the + sokol_app.h function sapp_frame_duration() + + Use the following functions to convert a duration in ticks into + useful time units: + + double stm_sec(uint64_t ticks); + double stm_ms(uint64_t ticks); + double stm_us(uint64_t ticks); + double stm_ns(uint64_t ticks); + Converts a tick value into seconds, milliseconds, microseconds + or nanoseconds. Note that not all platforms will have nanosecond + or even microsecond precision. + + Uses the following time measurement functions under the hood: + + Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() + MacOS/iOS: mach_absolute_time() + emscripten: emscripten_get_now() + Linux+others: clock_gettime(CLOCK_MONOTONIC) + + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +*/ + +#module_parameters(DEBUG := false, USE_GL := false, USE_DLL := false); + +#scope_export; + +#if OS == .WINDOWS { + #if USE_DLL { + #if USE_GL { + + #if DEBUG { sokol_time_clib :: #library "sokol_time_windows_x64_gl_debug"; } + else { sokol_time_clib :: #library "sokol_time_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_time_clib :: #library "sokol_time_windows_x64_d3d11_debug"; } + else { sokol_time_clib :: #library "sokol_time_windows_x64_d3d11_release"; } + } + } else { + #if USE_GL { + + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_windows_x64_gl_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_windows_x64_gl_release"; } + } else { + + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_windows_x64_d3d11_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_windows_x64_d3d11_release"; } + } + } +} +else #if OS == .MACOS { + #if USE_DLL { + #if USE_GL && CPU == .ARM64 && DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib"; } + else #if USE_GL && CPU == .ARM64 && !DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_arm64_gl_release.dylib"; } + else #if USE_GL && CPU == .X64 && DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_debug.dylib"; } + else #if USE_GL && CPU == .X64 && !DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_x64_gl_release.dylib"; } + else #if !USE_GL && CPU == .ARM64 && DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .ARM64 && !DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_arm64_metal_release.dylib"; } + else #if !USE_GL && CPU == .X64 && DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_debug.dylib"; } + else #if !USE_GL && CPU == .X64 && !DEBUG { sokol_time_clib :: #library "../dylib/sokol_dylib_macos_x64_metal_release.dylib"; } + } else { + #if USE_GL { + + #if CPU == .ARM64 { + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_macos_arm64_gl_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_macos_arm64_gl_release"; } + } else { + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_macos_x64_gl_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_macos_x64_gl_release"; } + } + } else { + + #if CPU == .ARM64 { + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_macos_arm64_metal_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_macos_arm64_metal_release"; } + } else { + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_macos_x64_metal_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_macos_x64_metal_release"; } + } + } + } +} else #if OS == .LINUX { + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_linux_x64_gl_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_linux_x64_gl_release"; } +} else #if OS == .WASM { + #if DEBUG { sokol_time_clib :: #library,no_dll "sokol_time_wasm_gl_debug"; } + else { sokol_time_clib :: #library,no_dll "sokol_time_wasm_gl_release"; } +} else { + log_error("This OS is currently not supported"); +} + +stm_setup :: () -> void #foreign sokol_time_clib; +stm_now :: () -> u64 #foreign sokol_time_clib; +stm_diff :: (new_ticks: u64, old_ticks: u64) -> u64 #foreign sokol_time_clib; +stm_since :: (start_ticks: u64) -> u64 #foreign sokol_time_clib; +stm_laptime :: (last_time: *u64) -> u64 #foreign sokol_time_clib; +stm_round_to_common_refresh_rate :: (frame_ticks: u64) -> u64 #foreign sokol_time_clib; +stm_sec :: (ticks: u64) -> float64 #foreign sokol_time_clib; +stm_ms :: (ticks: u64) -> float64 #foreign sokol_time_clib; +stm_us :: (ticks: u64) -> float64 #foreign sokol_time_clib; +stm_ns :: (ticks: u64) -> float64 #foreign sokol_time_clib; + diff --git a/resources/DroidSerif-Regular.ttf b/resources/DroidSerif-Regular.ttf new file mode 100644 index 0000000..239ba38 Binary files /dev/null and b/resources/DroidSerif-Regular.ttf differ diff --git a/src/arbtri.jai b/src/arbtri.jai new file mode 100644 index 0000000..2936a4b --- /dev/null +++ b/src/arbtri.jai @@ -0,0 +1,61 @@ +Arb_Tri :: struct { + pos: [3]Vector3; + col: [3]Vector4; + uv: [3]Vector2; +} + +Arb_Tri_State :: struct { + active : bool = false; + trilist : [..]Arb_Tri; +} + +arbTriState : Arb_Tri_State; + +arb_tri_add :: (tri: Arb_Tri) { + if !arbTriState.active { + array_reset_keeping_memory(*arbTriState.trilist); + arbTriState.active = true; + } + + array_add(*arbTriState.trilist, tri); +} + +arb_tri_flush :: () { + if !arbTriState.active { + return; + } + + + transform_to_screen_x :: (coord: float) -> float { + w, h := get_window_size(); + return (coord / cast(float) w) * 2.0 - 1.0; + } + + transform_to_screen_y :: (coord: float) -> float { + w, h := get_window_size(); + return (coord / cast(float) h) * 2.0 - 1.0; + } + + arbTriState.active = false; + + for tri, i : arbTriState.trilist { + bgn := i * 3 * 9; + for 0..2 { + gArbtriMem[bgn + it * 9 + 0] = transform_to_screen_x(tri.pos[it].x); + gArbtriMem[bgn + it * 9 + 1] = transform_to_screen_y(tri.pos[it].y) * -1.0; + gArbtriMem[bgn + it * 9 + 2] = 0; + gArbtriMem[bgn + it * 9 + 3] = tri.col[it].x; + gArbtriMem[bgn + it * 9 + 4] = tri.col[it].y; + gArbtriMem[bgn + it * 9 + 5] = tri.col[it].z; + gArbtriMem[bgn + it * 9 + 6] = tri.col[it].w; + gArbtriMem[bgn + it * 9 + 7] = tri.uv[it].x; + gArbtriMem[bgn + it * 9 + 8] = tri.uv[it].y; + } + } + + sg_update_buffer(gPipelines.arbtri.bind.vertex_buffers[0], *(sg_range.{ ptr = gArbtriMem.data, size = size_of(type_of(gArbtriMem)) })); + + sg_apply_pipeline(gPipelines.arbtri.pipeline); + sg_apply_bindings(*gPipelines.arbtri.bind); + sg_draw(0, xx (arbTriState.trilist.count * 3), 1); +} diff --git a/src/buffers.jai b/src/buffers.jai new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/buffers.jai @@ -0,0 +1 @@ + diff --git a/src/events.jai b/src/events.jai new file mode 100644 index 0000000..aa73be8 --- /dev/null +++ b/src/events.jai @@ -0,0 +1,9 @@ +mpos : Vector2; + +handle_event :: (e: *sapp_event) { + if e.type == .MOUSE_MOVE { + mpos.x = e.mouse_x; + mpos.y = e.mouse_y; + } + +} diff --git a/src/load.jai b/src/load.jai new file mode 100644 index 0000000..beb4aa2 --- /dev/null +++ b/src/load.jai @@ -0,0 +1,20 @@ +MAX_FILE_SIZE :: 200_000; +buf : [MAX_FILE_SIZE]u8; + +init_font_loads :: () { + print("SENDING LOAD!!!!\n"); + sfetch_send(*(sfetch_request_t.{ + path = "./resources/DroidSerif-Regular.ttf".data, + callback = fontcb, + buffer = .{ + ptr = buf.data, + size = buf.count + } + })); +} + +fontcb :: (res: *sfetch_response_t) #c_call { + push_context,defer_pop default_context; + print("RDY! Finished? % Fetched? % \n", res.fetched, res.finished); + state.font_default = fonsAddFontMem(state.fons, "sans", res.data.ptr, xx res.data.size, 0); +} diff --git a/src/main.jai b/src/main.jai new file mode 100644 index 0000000..d275f78 --- /dev/null +++ b/src/main.jai @@ -0,0 +1,100 @@ +#import "Basic"; +#import "Math"; +#load "ui/ui.jai"; +#load "pipelines.jai"; +#load "time.jai"; +#load "arbtri.jai"; +#load "events.jai"; +#load "load.jai"; +#load "./shaders/jai/shader_triangle.jai"; + +state: struct { + pass_action: sg_pass_action; + dpi_scale: float; + fons: *FONScontext; + font_default: s32; +}; + +Window_Info :: struct { + width: s32; + height: s32; + title: *u8; +}; + +get_window_info :: () -> Window_Info { + return Window_Info.{ + 1200, + 1200, + "trueno!" + }; +} + +round_pow2 :: (v: float) -> s32 { + vi := (cast(u32) v) - 1; + for i : 0..4 { + vi |= (vi >> (1< (s32, s32) { + return sapp_width(), sapp_height(); +} diff --git a/src/pipelines.jai b/src/pipelines.jai new file mode 100644 index 0000000..4336bf2 --- /dev/null +++ b/src/pipelines.jai @@ -0,0 +1,27 @@ +Pipeline_Binding :: struct { + pipeline: sg_pipeline; + bind: sg_bindings; +} + +gPipelines : struct { + arbtri: Pipeline_Binding; +} + +create_pipelines :: () { + create_arbtri_pipeline(); +} + +gArbtriMem : [1000*3*9]float; + +create_arbtri_pipeline :: () { + pipeline: sg_pipeline_desc; + shader_desc := triangle_shader_desc(sg_query_backend()); + pipeline.shader = sg_make_shader(*shader_desc); + pipeline.layout.attrs[ATTR_triangle_position] = .{ format = .FLOAT3 }; + pipeline.layout.attrs[ATTR_triangle_color0] = .{ format = .FLOAT4 }; + pipeline.layout.attrs[ATTR_triangle_uv] = .{ format = .FLOAT2 }; + gPipelines.arbtri.pipeline = sg_make_pipeline(*pipeline); + + buffer := sg_buffer_desc.{ usage = .DYNAMIC, size = size_of(type_of(gArbtriMem)) }; + gPipelines.arbtri.bind.vertex_buffers[0] = sg_make_buffer(*buffer); +} diff --git a/src/platform_specific/common.jai b/src/platform_specific/common.jai new file mode 100644 index 0000000..c5cb801 --- /dev/null +++ b/src/platform_specific/common.jai @@ -0,0 +1,52 @@ +#import,dir "../../modules/sokol-jai/sokol/app"; +#import,dir "../../modules/sokol-jai/sokol/gfx"; +#import,dir "../../modules/sokol-jai/sokol/gl"; +#import,dir "../../modules/sokol-jai/sokol/glue"; +#import,dir "../../modules/sokol-jai/sokol/fontstash"; +#import,dir "../../modules/sokol-jai/sokol/log"; +#import,dir "../../modules/sokol-jai/sokol/time"; +#import,dir "../../modules/sokol-jai/sokol/fetch"; + +#load "../main.jai"; + +default_context: #Context; + +sapp_init :: () { + push_context,defer_pop default_context; + context.logger = logger; + + wi := get_window_info(); + + sapp_run(*(sapp_desc.{ + init_cb = init_plat, + frame_cb = frame_plat, + cleanup_cb = cleanup_plat, + event_cb = event_plat, + width = wi.width, + height = wi.height, + window_title = wi.title, + // icon = .{ sokol_default = true }, + logger = .{ func = slog_func }, + })); +} + +init_plat :: () #c_call { + push_context,defer_pop default_context; + init(); +} + +frame_plat :: () #c_call { + push_context,defer_pop default_context; + frame(); +} + +event_plat :: (e: *sapp_event) #c_call { + push_context,defer_pop default_context; + handle_event(e); +} + +cleanup_plat :: () #c_call { + push_context,defer_pop default_context; + cleanup(); +} + diff --git a/src/platform_specific/main.c b/src/platform_specific/main.c new file mode 100644 index 0000000..b525384 --- /dev/null +++ b/src/platform_specific/main.c @@ -0,0 +1,9 @@ +#include +#include + +extern void _main(); + +int main() { + _main(); + return 0; +} \ No newline at end of file diff --git a/src/platform_specific/main_native.jai b/src/platform_specific/main_native.jai new file mode 100644 index 0000000..7f1d5d5 --- /dev/null +++ b/src/platform_specific/main_native.jai @@ -0,0 +1,18 @@ +#load "common.jai"; + +main :: () { + push_context,defer_pop default_context; + sapp_init(); +} + +log_warn :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) { + log(format_string, ..args, loc = loc, flags = flags | .WARNING, user_flags = user_flags, section = section); +} @PrintLike + +log_content :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) { + log(format_string, ..args, loc = loc, flags = flags | .CONTENT, user_flags = user_flags, section = section); +} @PrintLike + +logger :: (message: string, data: *void, info: Log_Info) { + print("%\n", message); +} diff --git a/src/platform_specific/main_web.jai b/src/platform_specific/main_web.jai new file mode 100644 index 0000000..1d8d29d --- /dev/null +++ b/src/platform_specific/main_web.jai @@ -0,0 +1,20 @@ +#load "common.jai"; + +#program_export +_main :: () #c_call { + push_context,defer_pop default_context; + sapp_init(); +} + +log_warn :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) { + log(format_string, ..args, loc = loc, flags = flags | .WARNING, user_flags = user_flags, section = section); +} @PrintLike + +log_content :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) { + log(format_string, ..args, loc = loc, flags = flags | .CONTENT, user_flags = user_flags, section = section); +} @PrintLike + +logger :: (message: string, data: *void, info: Log_Info) { + wasm_write_string :: (count: s64, data: *void, log_flag: Log_Flags) #foreign; // You will need to provide this function as JS code in your WASM environment. :JaiWasm: + wasm_write_string(message.count, message.data, info.common_flags); +} diff --git a/src/platform_specific/runtime.js b/src/platform_specific/runtime.js new file mode 100644 index 0000000..5ed4d73 --- /dev/null +++ b/src/platform_specific/runtime.js @@ -0,0 +1,26 @@ +mergeInto(LibraryManager.library, { + $UTF8ToString__sig: 'ip', // Override the signature of UTF8ToString to use BigInt instead of Number + + wasm_write_string: (s_count, s_data, log_flag) => { + function js_string_from_jai_string(pointer, length) { + const text_decoder = new TextDecoder(); + const u8 = new Uint8Array(wasmMemory.buffer) + const bytes = u8.subarray(Number(pointer), Number(pointer) + Number(length)); + return text_decoder.decode(bytes); + } + + const string = js_string_from_jai_string(s_data, s_count); + + switch (log_flag) { + case /* ERROR */ 0x1: { console.error(string); } break; + case /* WARNING */ 0x2: { console.warn(string); } break; + case /* CONTENT */ 0x4: { console.info(`%c${string}`, "color: #3949ab;"); } break; + default: { console.log(string); } + } + // Module.print(string); + }, + + wasm_debug_break: () => { + debugger; + }, +}); \ No newline at end of file diff --git a/src/platform_specific/shell.html b/src/platform_specific/shell.html new file mode 100644 index 0000000..8a89cb2 --- /dev/null +++ b/src/platform_specific/shell.html @@ -0,0 +1,79 @@ + + + + +${name} + + + +
${name} + home +
+ + + {{{ SCRIPT }}} + + diff --git a/src/shaders/compile_shaders.sh b/src/shaders/compile_shaders.sh new file mode 100755 index 0000000..6707221 --- /dev/null +++ b/src/shaders/compile_shaders.sh @@ -0,0 +1,5 @@ +for filename in *.glsl; do + if [ -f "$filename" ]; then + ./sokol-shdc -i "$filename" -o "./jai/${filename/.glsl/.jai}" -l glsl430:glsl300es -f sokol_jai + fi +done diff --git a/src/shaders/jai/shader_triangle.jai b/src/shaders/jai/shader_triangle.jai new file mode 100644 index 0000000..4eff420 --- /dev/null +++ b/src/shaders/jai/shader_triangle.jai @@ -0,0 +1,175 @@ +/* + #version:1# (machine generated, don't edit!) + + Generated by sokol-shdc (https://github.com/floooh/sokol-tools) + + Cmdline: + sokol-shdc -i shader_triangle.glsl -o ./jai/shader_triangle.jai -l glsl430:glsl300es -f sokol_jai + + Overview: + ========= + Shader program: 'triangle': + Get shader desc: triangle_shader_desc(sg_query_backend()) + Vertex Shader: vs + Fragment Shader: fs + Attributes: + ATTR_triangle_position => 0 + ATTR_triangle_color0 => 1 + ATTR_triangle_uv => 2 + Bindings: +*/ +ATTR_triangle_position :: 0; +ATTR_triangle_color0 :: 1; +ATTR_triangle_uv :: 2; +/* + #version 430 + + layout(location = 0) in vec4 position; + layout(location = 0) out vec4 color; + layout(location = 1) in vec4 color0; + layout(location = 2) in vec4 uv; + + void main() + { + gl_Position = position; + color = color0; + color.x = fma(9.9999999392252902907785028219223e-09, uv.x, color.x); + } + +*/ +vs_source_glsl430 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x33,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74, + 0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69, + 0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a, + 0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20, + 0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x2e,0x78,0x20,0x3d,0x20,0x66,0x6d,0x61, + 0x28,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x33,0x39,0x32,0x32,0x35,0x32, + 0x39,0x30,0x32,0x39,0x30,0x37,0x37,0x38,0x35,0x30,0x32,0x38,0x32,0x31,0x39,0x32, + 0x32,0x33,0x65,0x2d,0x30,0x39,0x2c,0x20,0x75,0x76,0x2e,0x78,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x2e,0x78,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 430 + + layout(location = 0) out vec4 frag_color; + layout(location = 0) in vec4 color; + + void main() + { + frag_color = color; + } + +*/ +fs_source_glsl430 := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x33,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20, + 0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69, + 0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 300 es + + layout(location = 0) in vec4 position; + out vec4 color; + layout(location = 1) in vec4 color0; + layout(location = 2) in vec4 uv; + + void main() + { + gl_Position = position; + color = color0; + color.x = 9.9999999392252902907785028219223e-09 * uv.x + color.x; + } + +*/ +vs_source_glsl300es := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20, + 0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a,0x0a,0x76, + 0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x2e,0x78,0x20,0x3d,0x20,0x39,0x2e,0x39,0x39,0x39, + 0x39,0x39,0x39,0x39,0x33,0x39,0x32,0x32,0x35,0x32,0x39,0x30,0x32,0x39,0x30,0x37, + 0x37,0x38,0x35,0x30,0x32,0x38,0x32,0x31,0x39,0x32,0x32,0x33,0x65,0x2d,0x30,0x39, + 0x20,0x2a,0x20,0x75,0x76,0x2e,0x78,0x20,0x2b,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x2e, + 0x78,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +/* + #version 300 es + precision mediump float; + precision highp int; + + layout(location = 0) out highp vec4 frag_color; + in highp vec4 color; + + void main() + { + frag_color = color; + } + +*/ +fs_source_glsl300es := u8.[ + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65, + 0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x69, + 0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29, + 0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +]; +triangle_shader_desc :: (backend: sg_backend) -> sg_shader_desc { + desc: sg_shader_desc; + desc.label = "triangle_shader"; + if backend == { + case .GLCORE; + desc.vertex_func.source = xx *vs_source_glsl430; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_source_glsl430; + desc.fragment_func.entry = "main"; + desc.attrs[0].base_type = .FLOAT; + desc.attrs[0].glsl_name = "position"; + desc.attrs[1].base_type = .FLOAT; + desc.attrs[1].glsl_name = "color0"; + desc.attrs[2].base_type = .FLOAT; + desc.attrs[2].glsl_name = "uv"; + case .GLES3; + desc.vertex_func.source = xx *vs_source_glsl300es; + desc.vertex_func.entry = "main"; + desc.fragment_func.source = xx *fs_source_glsl300es; + desc.fragment_func.entry = "main"; + desc.attrs[0].base_type = .FLOAT; + desc.attrs[0].glsl_name = "position"; + desc.attrs[1].base_type = .FLOAT; + desc.attrs[1].glsl_name = "color0"; + desc.attrs[2].base_type = .FLOAT; + desc.attrs[2].glsl_name = "uv"; + } + return desc; +} diff --git a/src/shaders/shader_triangle.glsl b/src/shaders/shader_triangle.glsl new file mode 100644 index 0000000..82db2f0 --- /dev/null +++ b/src/shaders/shader_triangle.glsl @@ -0,0 +1,24 @@ +@vs vs +in vec4 position; +in vec4 color0; +in vec4 uv; + +out vec4 color; + +void main() { + gl_Position = position; + color = color0; + color.x += 0.00000001 * uv.x; +} +@end + +@fs fs +in vec4 color; +out vec4 frag_color; + +void main() { + frag_color = color; +} +@end + +@program triangle vs fs diff --git a/src/shaders/sokol-shdc b/src/shaders/sokol-shdc new file mode 100755 index 0000000..2ce2e7e Binary files /dev/null and b/src/shaders/sokol-shdc differ diff --git a/src/time.jai b/src/time.jai new file mode 100644 index 0000000..4049a6d --- /dev/null +++ b/src/time.jai @@ -0,0 +1,3 @@ +get_time :: () -> float64 { + return stm_sec(stm_now()); +} diff --git a/src/ui/ui.jai b/src/ui/ui.jai new file mode 100644 index 0000000..50d4a15 --- /dev/null +++ b/src/ui/ui.jai @@ -0,0 +1,180 @@ +GR :: #import "GetRect_LeftHanded"()(Type_Indicator = Ui_Type_Indicator); + +Ui_Font :: struct { + em_width: u32 = 1; + character_height: u32 = 30; +} + +Ui_Texture :: struct { + name: string; +} + +Ui_Rect :: struct { + x, y, w, h: s32; +}; + +Ui_Type_Indicator :: struct { + Texture : Type : Ui_Texture; + Window_Type: Type : s32; + Font: Type: Ui_Font; + Font_Effects: Type: u32; +}; + +Font :: Ui_Font; + +defaultFont: Font; + +ui_texture_counter : u32 = 0; + +texture_load_from_memory :: (texture: *Ui_Texture, memory: []u8, srgb: bool, build_mipmaps: bool) -> bool { + // texname := tprint("ui_tex_%", ui_texture_counter); + // load_png_texture_from_memory(*GpuContext, copy_string(texname), memory); + // ui_texture_counter += 1; + // texture.name = copy_string(texname); + return true; +} + +gScissor : Ui_Rect; +gScissorActive : bool = false; + +get_render_size :: () -> (s32, s32) { + return 700, 700; +} + +set_scissor :: (x0: s32, y0: s32, x1: s32, y1: s32) { + w,h := get_render_size(); + gScissor = .{x0, y0, x1 - x0, y1 - y0}; + gScissorActive = true; +} +clear_scissor :: () { + gScissorActive = false; +} + +gCurrentTexture : *Ui_Texture = null; + +set_shader_for_color :: (enable_blend := false) { + // immediate_flush(); // We want to do the flush so we can set the sampler for the entire set. + // gCurrentTexture = null; +} + + +set_shader_for_images :: (texture: *Ui_Texture) { + // if ENABLE_UI_DEBUG_LOGGING { print("Setting shader for textures.."); } + // immediate_flush(); // We want to do the flush so we can set the sampler for the entire set. + // gCurrentTexture = texture; +} + +gPreppedText: string; +gPreppedTextWidth : s32; + +prepare_text :: (font: *Ui_Type_Indicator.Font, text: string, effects: Ui_Type_Indicator.Font_Effects = 0) -> s64 { + fonsSetFont(state.fons, state.font_default); + fonsSetSize(state.fons, xx font.character_height); + w := fonsTextBounds(state.fons, 0.0, 0.0, text.data, text.data + text.count, null); + gPreppedText = text; + gPreppedTextWidth = cast(s32) w; + return cast(s64) w; +} +draw_prepared_text :: (font: *Ui_Type_Indicator.Font, x: s64, y: s64, text_color: Vector4, effects: Ui_Type_Indicator.Font_Effects = 0) { + color := sfons_rgba(xx (255.0 * text_color.x), xx (255.0 * text_color.y), xx (255.0 * text_color.z), xx (255.0 * text_color.w)); + fonsSetColor(state.fons, color); + result := cast(*u8) temporary_alloc(gPreppedText.count + 1); // Add 1 for the zero. + memcpy(result, gPreppedText.data, gPreppedText.count); + result[gPreppedText.count] = 0; + fonsDrawText(state.fons, xx x, xx y, result, null); +} +get_mouse_pointer_position :: (window: Ui_Type_Indicator.Window_Type, right_handed: bool) -> (x: int, y: int, success: bool) { + return xx mpos.x, xx mpos.y, true; +} +get_font_at_size :: (memory: [] u8, pixel_height: int) -> *Font { + f : *Font = New(Font); + // f.character_height = cast(u32) pixel_height; + // f.em_width = cast(u32) get_font_letter_width(109, cast (s32) pixel_height); + return f; +} + +// TODO: Figure out what to do with the normal? +immediate_triangle :: (p0: Vector3, p1: Vector3, p2: Vector3, c0 := Vector4.{1,1,1,1}, c1 := Vector4.{1,1,1,1}, c2 := Vector4.{1,1,1,1}, uv0 := Vector2.{}, uv1 := Vector2.{}, uv2 := Vector2.{}, normal := Vector3.{z=1}) { + tri: Arb_Tri; + tri.pos[0] = p0; + tri.pos[1] = p1; + tri.pos[2] = p2; + + tri.col[0] = c0; + tri.col[1] = c1; + tri.col[2] = c2; + + // This UV symbolizes that the sampler should not be used. + nullUV : Vector2 = .{-4, -2}; + + if gCurrentTexture == null { + tri.uv[0] = nullUV; + tri.uv[1] = nullUV; + tri.uv[2] = nullUV; + } else { + tri.uv[0] = uv0; + tri.uv[1] = uv2; + tri.uv[2] = uv1; + } + + arb_tri_add(tri); +} + +immediate_quad :: (p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, color := Vector4.{1,1,1,1}, uv0 := Vector2.{0,0}, uv1 := Vector2.{1,0}, uv2 := Vector2.{1,1}, uv3 := Vector2.{0, 1}) { + to_3d_vec :: (v: Vector2) -> Vector3 { + return .{v.x, v.y, 0.0}; + } + immediate_triangle(to_3d_vec(p0), to_3d_vec(p1), to_3d_vec(p2), color, color, color, uv0, uv1, uv2); + immediate_triangle(to_3d_vec(p0), to_3d_vec(p2), to_3d_vec(p3), color, color, color, uv0, uv2, uv3); +} + +immediate_flush :: () { + // arb_tri_flush(); +} + +init_ui :: () { + dp : GR.Draw_Procs = .{ + texture_load_from_memory = texture_load_from_memory, + set_scissor = set_scissor, + clear_scissor = clear_scissor, + set_shader_for_color = set_shader_for_color, + set_shader_for_images = set_shader_for_images, + prepare_text = prepare_text, + draw_prepared_text = draw_prepared_text, + get_mouse_pointer_position = get_mouse_pointer_position, + get_font_at_size = get_font_at_size, + immediate_triangle = immediate_triangle, // implemented + immediate_quad = immediate_quad, // implemented + immediate_flush = immediate_flush // implemented + }; + + GR.ui_init("", *dp); +} + +tick_ui :: () { + w,h := get_window_size(); + GR.ui_per_frame_update(1, xx w, xx h, get_time()); +} + +checkboxTest : bool = false; + +get_font_at_size :: (pixel_height: int) -> *Font { + list : []u8; + return get_font_at_size(list, pixel_height); +} + +idk : bool; + +render_ui :: () { + proc := GR.default_theme_procs[3]; + my_theme := proc(); + GR.set_default_theme(my_theme); // Just in case we don't explicitly pass themes sometimes...! + + r := GR.get_rect(10, 10, 400, 30); + pressed := GR.button(r, "GetRect render lfg!!", *my_theme.button_theme); + + r.y += 150; + GR.base_checkbox(r, "CHECK!!!", idk, null); +} + + diff --git a/walloc.o b/walloc.o new file mode 100755 index 0000000..a3252cc Binary files /dev/null and b/walloc.o differ