diff --git a/.gitignore b/.gitignore
index 7a505e9..657965f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
.vscode
ols.json
build
-thirdparty/freetype
-thirdparty/harfbuzz
-thirdparty/sokol
-thirdparty/sokol-tools
+# thirdparty/harfbuzz
+# thirdparty/sokol
+# thirdparty/sokol-tools
diff --git a/thirdparty/harfbuzz/README.md b/thirdparty/harfbuzz/README.md
new file mode 100644
index 0000000..b306946
--- /dev/null
+++ b/thirdparty/harfbuzz/README.md
@@ -0,0 +1,10 @@
+# harfbuzz-odin
+
+Harbuzz bindings for odin.
+
+Its not the full amount, just enough to utilize its base shaping functionality.
+
+## scripts/build.ps1
+
+I only have support for building on Windows & Linux. However, Mac and Linux technically can just reference the library from their respective package managers.
+Will pull the latest source from the harfbuzz repository and build requisite libraries. Adjust the code as needed, by default a custom unity build is done (see `Build-RepoWithoutMeson`).
diff --git a/thirdparty/harfbuzz/harfbuzz.odin b/thirdparty/harfbuzz/harfbuzz.odin
new file mode 100644
index 0000000..bfbebe5
--- /dev/null
+++ b/thirdparty/harfbuzz/harfbuzz.odin
@@ -0,0 +1,345 @@
+/*
+NOTE(Ed): These bindings are currently partial for usage in a VE Font Cache port.
+*/
+package harfbuzz
+
+import "core:c"
+
+when ODIN_OS == .Windows {
+ // @(extra_linker_flags="/NODEFAULTLIB:msvcrt")
+ // foreign import harfbuzz "./lib/win64/libharfbuzz-0.dll"
+ foreign import harfbuzz "./lib/win64/harfbuzz.lib"
+ // foreign import harfbuzz "./lib/win64/libharfbuzz.a"
+}
+else when ODIN_OS == .Linux {
+ // foreign import harfbuzz "./lib/linux64/libharfbuzz.so"
+ foreign import harfbuzz "system:harfbuzz"
+}
+else when ODIN_OS == .Darwin {
+ // foreign import harfbuzz { "./lib/osx/libharfbuzz.so" }
+ foreign import harfbuzz "system:harfbuzz"
+}
+
+Buffer :: distinct rawptr // hb_buffer_t*
+Blob :: distinct rawptr // hb_blob_t*
+Codepoint :: distinct c.uint32_t // hb_codepoint_t
+Face :: distinct rawptr // hb_face_t*
+Font :: distinct rawptr // hb_font_t*
+Language :: distinct rawptr // hb_language_t*
+Mask :: distinct c.uint32_t // hb_mask_t
+Position :: distinct c.uint32_t // hb_position_t
+Tag :: distinct c.uint32_t // hb_tag_t
+Unicode_Funcs :: distinct rawptr // hb_unicode_funcs_t*
+
+hb_var_int_t :: struct #raw_union {
+ u32 : c.uint32_t,
+ i32 : c.int32_t,
+ u16 : [2]c.uint16_t,
+ i16 : [2]c.int16_t,
+ u8 : [4]c.uint8_t,
+ i8 : [4]c.int8_t,
+}
+
+Feature :: struct {
+ tag : Tag,
+ value : c.uint32_t,
+ start : c.uint,
+ end : c.uint,
+}
+
+Glyph_Info :: struct {
+ codepoint : Codepoint,
+ /*< private >*/
+ mask : Mask,
+ /*< public >*/
+ cluster : c.uint32_t,
+
+ /*< private >*/
+ var1 : hb_var_int_t,
+ var2 : hb_var_int_t,
+}
+
+Glyph_Position :: struct {
+ x_advance : Position,
+ y_advance : Position,
+ x_offset : Position,
+ y_offset : Position,
+
+ /*< private >*/
+ var : hb_var_int_t,
+}
+
+Segment_Properties :: struct {
+ direction : Direction,
+ script : Script,
+ language : Language,
+ reserved1 : rawptr,
+ reserved2 : rawptr,
+}
+
+Buffer_Content_Type :: enum c.uint {
+ INVALID = 0,
+ UNICODE,
+ GLYPHS
+}
+
+Direction :: enum c.uint {
+ INVALID = 0,
+ LGR = 4,
+ RTL,
+ TTB,
+ BTT,
+}
+
+Script :: enum u32 {
+ // ID = ((hb_tag_t)((((uint32_t)(c1)&0xFF)<<24)|(((uint32_t)(c2)&0xFF)<<16)|(((uint32_t)(c3)&0xFF)<<8)|((uint32_t)(c4)&0xFF))),
+
+ // 1.1
+ COMMON = ( u32('Z') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('y') & 0xFF ) << 8 | ( u32('y') & 0xFF ),
+ INHERITED = ( u32('Z') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('h') & 0xFF ),
+
+ // 5.0
+ UNKNOWN = ( u32('Z') & 0xFF ) << 24 | ( u32('z') & 0xFF ) << 16 | ( u32('z') & 0xFF ) << 8 | ( u32('z') & 0xFF ),
+
+ // 1.1
+ ARABIC = ( u32('A') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+ ARMENIAN = ( u32('A') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('n') & 0xFF ),
+ BENGALI = ( u32('B') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ CYRILLIC = ( u32('C') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('l') & 0xFF ),
+ DEVANAGARI = ( u32('D') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('v') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ GEORGIAN = ( u32('G') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('o') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ GREEK = ( u32('G') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('e') & 0xFF ) << 8 | ( u32('k') & 0xFF ),
+ GUJARATI = ( u32('G') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('j') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ GURMUKHI = ( u32('G') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('u') & 0xFF ),
+ HANGUL = ( u32('H') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ HAN = ( u32('H') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ HEBREW = ( u32('H') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('b') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ HIRAGANA = ( u32('H') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ KANNADA = ( u32('K') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('d') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ KATAKANA = ( u32('K') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ LAO = ( u32('L') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('o') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ LATIN = ( u32('L') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('t') & 0xFF ) << 8 | ( u32('n') & 0xFF ),
+ MALAYALAN = ( u32('M') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('y') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ ORIYA = ( u32('O') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('y') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ TAMIL = ( u32('T') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('l') & 0xFF ),
+ TELUGU = ( u32('T') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('u') & 0xFF ),
+ THAI = ( u32('T') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+
+ // 2.0
+ TIBETAN = ( u32('T') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('b') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+
+ // 3.0
+ BOPOMOFO = ( u32('B') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('p') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ BRAILLE = ( u32('B') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ CANADIAN_SYLLABICS = ( u32('C') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('s') & 0xFF ),
+ CHEROKEE = ( u32('C') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('e') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ ETHIOPIC = ( u32('E') & 0xFF ) << 24 | ( u32('t') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ KHMER = ( u32('K') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ MONGOLIAN = ( u32('M') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ MYANMAR = ( u32('M') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ OGHAM = ( u32('O') & 0xFF ) << 24 | ( u32('g') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('n') & 0xFF ),
+ RUNIC = ( u32('R') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ SINHALA = ( u32('S') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('h') & 0xFF ),
+ SYRIAC = ( u32('S') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('c') & 0xFF ),
+ THAANA = ( u32('T') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ YI = ( u32('Y') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('i') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+
+ // 3.1
+ DESERET = ( u32('D') & 0xFF ) << 24 | ( u32('s') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+ GOTHIC = ( u32('G') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('t') & 0xFF ) << 8 | ( u32('h') & 0xFF ),
+ OLD_ITALIC = ( u32('I') & 0xFF ) << 24 | ( u32('t') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('l') & 0xFF ),
+
+ // 3.2
+ BUHID = ( u32('B') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ HANUNOO = ( u32('H') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ TAGALOG = ( u32('T') & 0xFF ) << 24 | ( u32('g') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ TAGBANWA = ( u32('T') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+
+ // 4.0
+ CYPRIOT = ( u32('C') & 0xFF ) << 24 | ( u32('p') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+ LIMBU = ( u32('L') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+ LINEAR_B = ( u32('L') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('i') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+ OSMANYA = ( u32('O') & 0xFF ) << 24 | ( u32('m') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ SHAVIAN = ( u32('S') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('w') & 0xFF ),
+ TAI_LE = ( u32('T') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('e') & 0xFF ),
+ UGARITIC = ( u32('U') & 0xFF ) << 24 | ( u32('g') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+
+ // 4.1
+ BUGINESE = ( u32('B') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ COPTIC = ( u32('C') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('p') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+ GLAGOLITIC = ( u32('G') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ KHAROSHTHI = ( u32('K') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ NEW_TAI_LUE = ( u32('T') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('u') & 0xFF ),
+ OLD_PERSIAN = ( u32('X') & 0xFF ) << 24 | ( u32('p') & 0xFF ) << 16 | ( u32('e') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ SYLOTI_NAGRI = ( u32('S') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ TIFINAGH = ( u32('T') & 0xFF ) << 24 | ( u32('f') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+
+ // 5.0
+ BALINESE = ( u32('B') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ CUNEIFORM = ( u32('X') & 0xFF ) << 24 | ( u32('s') & 0xFF ) << 16 | ( u32('u') & 0xFF ) << 8 | ( u32('x') & 0xFF ),
+ NKO = ( u32('N') & 0xFF ) << 24 | ( u32('k') & 0xFF ) << 16 | ( u32('o') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ PHAGS_PA = ( u32('P') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ PHOENICIAN = ( u32('P') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('x') & 0xFF ),
+
+ // 5.1
+ CARIAN = ( u32('C') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ CHAM = ( u32('C') & 0xFF ) << 24 | ( u32('j') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ KAYAH_LI = ( u32('K') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ LEPCHA = ( u32('L') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('p') & 0xFF ) << 8 | ( u32('c') & 0xFF ),
+ LYCIAN = ( u32('L') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('c') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ LYDIAN = ( u32('L') & 0xFF ) << 24 | ( u32('y') & 0xFF ) << 16 | ( u32('d') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ OL_CHIKI = ( u32('O') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('c') & 0xFF ) << 8 | ( u32('k') & 0xFF ),
+ REJANG = ( u32('R') & 0xFF ) << 24 | ( u32('n') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ SAURASHTRA = ( u32('S') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('u') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ SUNDANESE = ( u32('S') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ VAI = ( u32('V') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('i') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+
+ // 5.2
+ AVESTAN = ( u32('A') & 0xFF ) << 24 | ( u32('v') & 0xFF ) << 16 | ( u32('s') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+ BAMUM = ( u32('B') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('u') & 0xFF ),
+ EGYPTIAN_HIEROGLYPHS = ( u32('E') & 0xFF ) << 24 | ( u32('g') & 0xFF ) << 16 | ( u32('y') & 0xFF ) << 8 | ( u32('p') & 0xFF ),
+ IMPERIAL_ARAMAIC = ( u32('A') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ INSCRIPTIONAL_PAHLAVI = ( u32('P') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ INSCRIPTIONAL_PARTHAIAN = ( u32('P') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('t') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ JAVANESE = ( u32('J') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('v') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ KAITHI = ( u32('K') & 0xFF ) << 24 | ( u32('t') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ LISU = ( u32('L') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('s') & 0xFF ) << 8 | ( u32('u') & 0xFF ),
+ MEETEI_MAYEK = ( u32('M') & 0xFF ) << 24 | ( u32('t') & 0xFF ) << 16 | ( u32('e') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ OLD_SOUTH_ARABIAN = ( u32('S') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+ OLD_TURKIC = ( u32('O') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('k') & 0xFF ) << 8 | ( u32('h') & 0xFF ),
+ SAMARITAN = ( u32('S') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('m') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ TAI_THAM = ( u32('L') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ TAI_VIET = ( u32('T') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('v') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+
+ // 6.0
+ BATAK = ( u32('B') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('t') & 0xFF ) << 8 | ( u32('k') & 0xFF ),
+ BRAHMI = ( u32('B') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('h') & 0xFF ),
+ MANDAIC = ( u32('M') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+
+ // 6.1
+ CHAKMA = ( u32('C') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('k') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ MEROITIC_CURSIVE = ( u32('M') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('c') & 0xFF ),
+ MEROITIC_HIEROGLYPHS = ( u32('M') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ MIAO = ( u32('P') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ SHARADA = ( u32('S') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ SORA_SOMPENG = ( u32('S') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ TAKRI = ( u32('T') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('k') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+
+ // 0.9.30
+ // 7.0
+ BASSA_VAH = ( u32('B') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('s') & 0xFF ) << 8 | ( u32('s') & 0xFF ),
+ CAUCASIAN_ALBANIAN = ( u32('A') & 0xFF ) << 24 | ( u32('g') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+ DUPLOYAN = ( u32('D') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('p') & 0xFF ) << 8 | ( u32('l') & 0xFF ),
+ ELBASAN = ( u32('E') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('b') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ GRANTHA = ( u32('G') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('n') & 0xFF ),
+ KHOJKI = ( u32('K') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('o') & 0xFF ) << 8 | ( u32('j') & 0xFF ),
+ KHUDAWADI = ( u32('S') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ LINEAR_A = ( u32('L') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ MAHAJANI = ( u32('M') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('j') & 0xFF ),
+ MANICHAEAN = ( u32('M') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ MENDE_KIKAKUI = ( u32('M') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ MODI = ( u32('M') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('d') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+ MRO = ( u32('M') & 0xFF ) << 24 | ( u32('r') & 0xFF ) << 16 | ( u32('o') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ NABATAEAN = ( u32('N') & 0xFF ) << 24 | ( u32('b') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+ OLD_NORTH_ARABIAN = ( u32('N') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('b') & 0xFF ),
+ OLD_PERMIC = ( u32('P') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ PAHAWH_HMONG = ( u32('H') & 0xFF ) << 24 | ( u32('m') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ PALMYRENE = ( u32('P') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ PAU_CIN_HAU = ( u32('P') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('u') & 0xFF ) << 8 | ( u32('c') & 0xFF ),
+ PSALTER_PAHLAVI = ( u32('P') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('p') & 0xFF ),
+ SIDDHAM = ( u32('S') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('d') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ TIRHUNTA = ( u32('T') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('h') & 0xFF ),
+ WARANG_CITI = ( u32('W') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+
+ // 8.0
+ AHOM = ( u32('A') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('o') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ ANATOLIAN_HIEROGLYPHS = ( u32('H') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('u') & 0xFF ) << 8 | ( u32('w') & 0xFF ),
+ HATRAN = ( u32('H') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('t') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ MULTANI = ( u32('M') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('t') & 0xFF ),
+ OLD_HUNGARIAN = ( u32('H') & 0xFF ) << 24 | ( u32('u') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ SIGNWRITING = ( u32('S') & 0xFF ) << 24 | ( u32('g') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('w') & 0xFF ),
+
+ // 1.3.0
+ // 9.0
+ ADLAM = ( u32('A') & 0xFF ) << 24 | ( u32('d') & 0xFF ) << 16 | ( u32('l') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ BHAIKSUKI = ( u32('B') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('k') & 0xFF ) << 8 | ( u32('s') & 0xFF ),
+ MARCHEN = ( u32('M') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('c') & 0xFF ),
+ OSAGE = ( u32('O') & 0xFF ) << 24 | ( u32('s') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('e') & 0xFF ),
+ TANGUT = ( u32('T') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ NEWA = ( u32('N') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('w') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+
+ // 1.6.0
+ // 10.0
+ MASARAM_GONDI = ( u32('D') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ NUSHU = ( u32('D') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ SOYOMBO = ( u32('D') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ ZANABAZAR_SQUARE = ( u32('D') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+
+ // 1.8.0
+ // 11.0
+ DOGRA = ( u32('D') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('r') & 0xFF ),
+ GUNJALA_GONDI = ( u32('G') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ HANIFI_ROHINGYA = ( u32('R') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('g') & 0xFF ),
+ MAKASAR = ( u32('M') & 0xFF ) << 24 | ( u32('k') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('a') & 0xFF ),
+ MEDEFAIDRIN = ( u32('M') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('d') & 0xFF ) << 8 | ( u32('f') & 0xFF ),
+ OLD_SOGDIAN = ( u32('S') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+ SOGDIAN = ( u32('S') & 0xFF ) << 24 | ( u32('o') & 0xFF ) << 16 | ( u32('g') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+
+ // 2.4.0
+ // 12.0
+ ELYMAIC = ( u32('E') & 0xFF ) << 24 | ( u32('l') & 0xFF ) << 16 | ( u32('y') & 0xFF ) << 8 | ( u32('m') & 0xFF ),
+ NANDINAGARI = ( u32('N') & 0xFF ) << 24 | ( u32('a') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('d') & 0xFF ),
+ NYIAKENG_PUACHUE_HMONG = ( u32('H') & 0xFF ) << 24 | ( u32('m') & 0xFF ) << 16 | ( u32('n') & 0xFF ) << 8 | ( u32('p') & 0xFF ),
+ WANCHO = ( u32('W') & 0xFF ) << 24 | ( u32('c') & 0xFF ) << 16 | ( u32('h') & 0xFF ) << 8 | ( u32('o') & 0xFF ),
+
+ // 2.6.7
+ // 13.0
+ CHRASMIAN = ( u32('C') & 0xFF ) << 24 | ( u32('h') & 0xFF ) << 16 | ( u32('r') & 0xFF ) << 8 | ( u32('s') & 0xFF ),
+ DIVES_AKURU = ( u32('D') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('a') & 0xFF ) << 8 | ( u32('k') & 0xFF ),
+ KHITAN_SMALL_SCRIPT = ( u32('K') & 0xFF ) << 24 | ( u32('i') & 0xFF ) << 16 | ( u32('t') & 0xFF ) << 8 | ( u32('s') & 0xFF ),
+ YEZIDI = ( u32('Y') & 0xFF ) << 24 | ( u32('e') & 0xFF ) << 16 | ( u32('z') & 0xFF ) << 8 | ( u32('i') & 0xFF ),
+
+ INVALID= 0,
+}
+
+Memory_Mode :: enum c.int {
+ DUPLICATE,
+ READONLY,
+ WRITABLE,
+ READONLY_MAY_MAKE_WRITABLE,
+}
+
+Destroy_Func :: proc "c" ( user_data : rawptr )
+
+@(default_calling_convention="c", link_prefix="hb_")
+foreign harfbuzz
+{
+ blob_create :: proc( data : [^]u8, length : c.uint, memory_mode : Memory_Mode, user_data : rawptr, destroy : Destroy_Func ) -> Blob ---
+ blob_destroy :: proc( blob : Blob ) ---
+
+ buffer_create :: proc() -> Buffer ---
+ buffer_destroy :: proc( buffer : Buffer ) ---
+ buffer_add :: proc( buffer : Buffer, codepoint : Codepoint, cluster : c.uint ) ---
+ buffer_clear_contents :: proc( buffer : Buffer ) ---
+ buffer_get_glyph_infos :: proc( buffer : Buffer, length : ^c.uint ) -> [^]Glyph_Info ---
+ buffer_get_glyph_positions :: proc( buffer : Buffer, length : ^c.uint ) -> [^]Glyph_Position ---
+ buffer_set_direction :: proc( buffer : Buffer, direction : Direction ) ---
+ buffer_set_language :: proc( buffer : Buffer, language : Language ) ---
+ buffer_set_script :: proc( buffer : Buffer, script : Script ) ---
+ buffer_set_content_type :: proc( buffer : Buffer, content_type : Buffer_Content_Type ) ---
+
+ face_create :: proc( blob : Blob, index : c.uint ) -> Face ---
+ face_destroy :: proc( face : Face ) ---
+
+ font_create :: proc( face : Face ) -> Font ---
+ font_destroy :: proc( font : Font ) ---
+
+ language_get_default :: proc() -> Language ---
+
+ script_get_horizontal_direction :: proc( script : Script ) -> Direction ---
+
+ shape :: proc( font : Font, buffer : Buffer, features : [^]Feature, num_features : c.uint ) ---
+
+ unicode_funcs_get_default :: proc() -> Unicode_Funcs ---
+ unicode_script :: proc( ufuncs : Unicode_Funcs, unicode : Codepoint ) -> Script ---
+}
diff --git a/thirdparty/harfbuzz/lib/linux64/libharfbuzz.so b/thirdparty/harfbuzz/lib/linux64/libharfbuzz.so
new file mode 100644
index 0000000..d438ace
Binary files /dev/null and b/thirdparty/harfbuzz/lib/linux64/libharfbuzz.so differ
diff --git a/thirdparty/harfbuzz/lib/win64/harfbuzz.dll b/thirdparty/harfbuzz/lib/win64/harfbuzz.dll
new file mode 100644
index 0000000..7e04e80
Binary files /dev/null and b/thirdparty/harfbuzz/lib/win64/harfbuzz.dll differ
diff --git a/thirdparty/harfbuzz/lib/win64/harfbuzz.lib b/thirdparty/harfbuzz/lib/win64/harfbuzz.lib
new file mode 100644
index 0000000..b982352
Binary files /dev/null and b/thirdparty/harfbuzz/lib/win64/harfbuzz.lib differ
diff --git a/thirdparty/harfbuzz/scripts/build.ps1 b/thirdparty/harfbuzz/scripts/build.ps1
new file mode 100644
index 0000000..7080ba7
--- /dev/null
+++ b/thirdparty/harfbuzz/scripts/build.ps1
@@ -0,0 +1,305 @@
+$misc = join-path $PSScriptRoot 'helpers/misc.ps1'
+. $misc
+
+$path_root = git rev-parse --show-toplevel
+$path_lib = join-path $path_root 'lib'
+$path_win64 = join-path $path_lib 'win64'
+
+$url_harfbuzz = 'https://github.com/harfbuzz/harfbuzz.git'
+$path_harfbuzz = join-path $path_root 'harfbuzz'
+
+function build-repo {
+ verify-path $script:path_lib
+ verify-path $path_win64
+
+ clone-gitrepo $path_harfbuzz $url_harfbuzz
+
+ push-location $path_harfbuzz
+
+ $library_type = "shared"
+ $build_type = "release"
+
+ # Meson configure and build
+ $mesonArgs = @(
+ "build",
+ "--default-library=$library_type",
+ "--buildtype=$build_type",
+ "--wrap-mode=forcefallback",
+ "-Dglib=disabled",
+ "-Dgobject=disabled",
+ "-Dcairo=disabled",
+ "-Dicu=disabled",
+ "-Dgraphite=disabled",
+ "-Dfreetype=disabled",
+ "-Ddirectwrite=disabled",
+ "-Dcoretext=disabled"
+ )
+ & meson $mesonArgs
+ & meson compile -C build
+
+ pop-location
+
+ $path_build = join-path $path_harfbuzz 'build'
+ $path_src = join-path $path_build 'src'
+ $path_dll = join-path $path_src 'harfbuzz.dll'
+ $path_lib = join-path $path_src 'harfbuzz.lib'
+ $path_lib_static = join-path $path_src 'libharfbuzz.a'
+ $path_pdb = join-path $path_src 'harfbuzz.pdb'
+
+ # Copy files based on build type and library type
+ if ($build_type -eq "debug") {
+ copy-item -Path $path_pdb -Destination $path_win64 -Force
+ }
+
+ if ($library_type -eq "static") {
+ copy-item -Path $path_lib_static -Destination (join-path $path_win64 'harfbuzz.lib') -Force
+ }
+ else {
+ copy-item -Path $path_lib -Destination $path_win64 -Force
+ copy-item -Path $path_dll -Destination $path_win64 -Force
+ }
+
+ write-host "Build completed and files copied to $path_win64"
+}
+# build-repo
+
+function Build-RepoWithoutMeson {
+ $devshell = join-path $PSScriptRoot 'helpers/devshell.ps1'
+ & $devshell -arch amd64
+
+ verify-path $script:path_lib
+ verify-path $path_win64
+
+ clone-gitrepo $path_harfbuzz $url_harfbuzz
+
+ $path_harfbuzz_build = join-path $path_harfbuzz 'build'
+ verify-path $path_harfbuzz_build
+
+ $library_type = "shared"
+ $build_type = "release"
+
+ push-location $path_harfbuzz
+
+ $compiler_args = @(
+ "/nologo",
+ "/W3",
+ "/D_CRT_SECURE_NO_WARNINGS",
+ "/DHAVE_FALLBACK=1",
+ "/DHAVE_OT=1",
+ "/DHAVE_SUBSET=1",
+ "/DHB_USE_INTERNAL_PARSER",
+ "/DHB_NO_COLOR",
+ "/DHB_NO_DRAW",
+ "/DHB_NO_PARSE",
+ "/DHB_NO_MT",
+ "/DHB_NO_GRAPHITE2",
+ "/DHB_NO_ICU",
+ "/DHB_NO_DIRECTWRITE",
+ "/I$path_harfbuzz\src",
+ "/I$path_harfbuzz"
+ )
+
+ if ( $library_type -eq "shared" ) {
+ $compiler_args += "/DHAVE_DECLSPEC"
+ $compiler_args += "/DHARFBUZZ_EXPORTS"
+ }
+
+ if ($build_type -eq "debug") {
+ $compiler_args += "/MDd", "/Od", "/Zi"
+ } else {
+ $compiler_args += "/MD", "/O2"
+ }
+
+ $compiler_args = $compiler_args -join " "
+
+ $config_h_content = @"
+#define HB_VERSION_MAJOR 9
+#define HB_VERSION_MINOR 0
+#define HB_VERSION_MICRO 0
+#define HB_VERSION_STRING "9.0.0"
+#define HAVE_ROUND 1
+#define HB_NO_BITMAP 1
+#define HB_NO_CFF 1
+#define HB_NO_OT_FONT_CFF 1
+#define HB_NO_SUBSET_CFF 1
+#define HB_HAVE_SUBSET 0
+#define HB_HAVE_OT 0
+#define HB_USER_DATA_KEY_DEFINE1(_name) extern HB_EXTERN hb_user_data_key_t _name
+"@
+ set-content -Path (join-path $path_harfbuzz "config.h") -Value $config_h_content
+
+ $unity_content = @"
+#define HB_EXTERN __declspec(dllexport)
+
+// base
+#include "config.h"
+#include "hb-aat-layout.cc"
+#include "hb-aat-map.cc"
+#include "hb-blob.cc"
+#include "hb-buffer-serialize.cc"
+#include "hb-buffer-verify.cc"
+#include "hb-buffer.cc"
+#include "hb-common.cc"
+
+//#include "hb-draw.cc"
+//#include "hb-paint.cc"
+//#include "hb-paint-extents.cc"
+
+#include "hb-face.cc"
+#include "hb-face-builder.cc"
+#include "hb-fallback-shape.cc"
+#include "hb-font.cc"
+#include "hb-map.cc"
+#include "hb-number.cc"
+#include "hb-ot-cff1-table.cc"
+#include "hb-ot-cff2-table.cc"
+#include "hb-ot-color.cc"
+#include "hb-ot-face.cc"
+#include "hb-ot-font.cc"
+
+#include "hb-outline.cc"
+#include "OT/Var/VARC/VARC.cc"
+
+#include "hb-ot-layout.cc"
+#include "hb-ot-map.cc"
+#include "hb-ot-math.cc"
+#include "hb-ot-meta.cc"
+#include "hb-ot-metrics.cc"
+#include "hb-ot-name.cc"
+
+#include "hb-ot-shaper-arabic.cc"
+#include "hb-ot-shaper-default.cc"
+#include "hb-ot-shaper-hangul.cc"
+#include "hb-ot-shaper-hebrew.cc"
+#include "hb-ot-shaper-indic-table.cc"
+#include "hb-ot-shaper-indic.cc"
+#include "hb-ot-shaper-khmer.cc"
+#include "hb-ot-shaper-myanmar.cc"
+#include "hb-ot-shaper-syllabic.cc"
+#include "hb-ot-shaper-thai.cc"
+#include "hb-ot-shaper-use.cc"
+#include "hb-ot-shaper-vowel-constraints.cc"
+
+#include "hb-ot-shape-fallback.cc"
+#include "hb-ot-shape-normalize.cc"
+#include "hb-ot-shape.cc"
+#include "hb-ot-tag.cc"
+#include "hb-ot-var.cc"
+
+#include "hb-set.cc"
+#include "hb-shape-plan.cc"
+#include "hb-shape.cc"
+#include "hb-shaper.cc"
+#include "hb-static.cc"
+#include "hb-style.cc"
+#include "hb-ucd.cc"
+#include "hb-unicode.cc"
+
+// libharfbuzz-subset
+//#include "hb-subset-input.cc"
+//#include "hb-subset-cff-common.cc"
+//#include "hb-subset-cff1.cc"
+//#include "hb-subset-cff2.cc"
+//#include "hb-subset-instancer-iup.cc"
+//#include "hb-subset-instancer-solver.cc"
+//#include "hb-subset-plan.cc"
+//#include "hb-subset-repacker.cc"
+
+//#include "graph/gsubgpos-context.cc"
+
+//#include "hb-subset.cc"
+"@
+ $unity_file = join-path $path_harfbuzz_build "harfbuzz_unity.cc"
+ set-content -Path $unity_file -Value $unity_content
+
+ # Compile unity file
+ $obj_file = "harfbuzz_unity.obj"
+ $command = "cl.exe $compiler_args /c $unity_file /Fo$path_harfbuzz_build\$obj_file"
+
+ write-host "Compiling: $command"
+ invoke-expression $command
+
+ if ($LASTEXITCODE -ne 0) {
+ write-error "Compilation failed for unity build"
+ pop-location
+ return
+ }
+
+ push-location $path_harfbuzz_build
+
+ # Create library
+ if ($library_type -eq "static")
+ {
+ $lib_command = "lib.exe /OUT:harfbuzz.lib $obj_file"
+
+ write-host "Creating static library: $lib_command"
+ invoke-expression $lib_command
+
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Static library creation failed"
+ pop-location
+ pop-location
+ return
+ }
+ $output_file = "harfbuzz.lib"
+ }
+ else
+ {
+ $linker_args = "/DLL", "/OUT:harfbuzz.dll"
+
+ if ($build_type -eq "debug") {
+ $linker_args += "/DEBUG"
+ }
+
+ $link_command = "link.exe $($linker_args -join ' ') $obj_file"
+
+ write-host "Creating shared library: $link_command"
+ invoke-expression $link_command
+
+ if ($LASTEXITCODE -ne 0) {
+ write-error "Shared library creation failed"
+ pop-location
+ pop-location
+ return
+ }
+ $output_file = "harfbuzz.dll"
+ }
+
+ pop-location # path_harfbuzz_build
+ pop-location # path_harfbuzz
+
+ # Copy files
+ $path_output = join-path $path_harfbuzz_build $output_file
+
+ if (test-path $path_output) {
+ copy-item -Path $path_output -Destination $path_win64 -Force
+ if ($library_type -eq "shared") {
+ $path_lib = join-path $path_harfbuzz_build "harfbuzz.lib"
+ if (test-path $path_lib) {
+ copy-item -Path $path_lib -Destination $path_win64 -Force
+ }
+ }
+ } else {
+ write-warning "Output file not found: $path_output"
+ }
+
+ write-host "Build completed and files copied to $path_win64"
+}
+Build-RepoWithoutMeson
+
+function grab-binaries {
+ verify-path $script:path_lib
+ verify-path $path_win64
+
+ $url_harfbuzz_8_5_0_win64 = 'https://github.com/harfbuzz/harfbuzz/releases/latest/download/harfbuzz-win64-8.5.0.zip'
+ $path_harfbuzz_win64_zip = join-path $path_win64 'harfbuzz-win64-8.5.0.zip'
+ $path_harfbuzz_win64 = join-path $path_win64 'harfbuzz-win64'
+
+ grab-zip $url_harfbuzz_8_5_0_win64 $path_harfbuzz_win64_zip $path_win64
+ get-childitem -path $path_harfbuzz_win64 | move-item -destination $path_win64 -force
+
+ # Clean up the ZIP file and the now empty harfbuzz-win64 directory
+ remove-item $path_harfbuzz_win64_zip -force
+ remove-item $path_harfbuzz_win64 -recurse -force
+}
+# grab-binaries
diff --git a/thirdparty/harfbuzz/scripts/build.sh b/thirdparty/harfbuzz/scripts/build.sh
new file mode 100644
index 0000000..68d8009
--- /dev/null
+++ b/thirdparty/harfbuzz/scripts/build.sh
@@ -0,0 +1,284 @@
+#!/bin/bash
+
+# Source the misc.sh script
+misc_script="$(dirname "$0")/helpers/misc.sh"
+chmod +x "$misc_script"
+source "$misc_script"
+
+path_root=$(git rev-parse --show-toplevel)
+path_lib="$path_root/lib"
+path_osx="$path_lib/osx"
+path_linux64="$path_lib/linux64"
+
+OS=$(uname -s)
+
+# Set the appropriate output directory and file extension
+case "$OS" in
+ Darwin*)
+ path_output="$path_osx"
+ shared_lib_extension="dylib"
+ ;;
+ Linux*)
+ path_output="$path_linux64"
+ shared_lib_extension="so"
+ ;;
+ *)
+ echo "Unsupported operating system: $OS"
+ exit 1
+ ;;
+esac
+
+url_harfbuzz='https://github.com/harfbuzz/harfbuzz.git'
+path_harfbuzz="$path_root/harfbuzz"
+
+build_repo() {
+ verify_path "$path_lib"
+ verify_path "$path_output"
+
+ # grab the actual repo
+ clone_gitrepo "$path_harfbuzz" "$url_harfbuzz"
+
+ pushd "$path_harfbuzz" > /dev/null
+
+ library_type="shared"
+ build_type="release"
+
+ # Check if meson is installed
+ if ! command -v meson &> /dev/null; then
+ echo "Meson is not installed. Please install it and try again."
+ exit 1
+ fi
+
+ # Meson configure and build
+ meson_args=(
+ "build"
+ "--default-library=$library_type"
+ "--buildtype=$build_type"
+ "--wrap-mode=forcefallback"
+ "-Dglib=disabled"
+ "-Dgobject=disabled"
+ "-Dcairo=disabled"
+ "-Dicu=disabled"
+ "-Dgraphite=disabled"
+ "-Dfreetype=disabled"
+ "-Ddirectwrite=disabled"
+ "-Dcoretext=disabled"
+ )
+ meson "${meson_args[@]}"
+ ninja -C build
+
+ popd > /dev/null
+
+ path_build="$path_harfbuzz/build"
+ path_src="$path_build/src"
+ path_so="$path_src/libharfbuzz.so"
+ path_a="$path_src/libharfbuzz.a"
+
+ # Copy files based on build type and library type
+ if [ "$build_type" = "debug" ]; then
+ # Debug symbols are typically embedded in the .so file on Linux
+ # If there's a separate debug file, you would copy it here
+ :
+ fi
+
+ if [ "$library_type" = "static" ]; then
+ cp "$path_a" "$path_linux64/libharfbuzz.a"
+ else
+ cp "$path_so" "$path_linux64/libharfbuzz.so"
+ fi
+
+ echo "Build completed and files copied to $path_linux64"
+}
+
+build_repo_without_meson() {
+ # Detect the operating system
+ OS=$(uname -s)
+
+ echo $url_harfbuzz
+ echo $path_harfbuzz
+ echo $path_lib
+ echo $path_linux64
+ verify_path "$path_lib"
+ verify_path "$path_linux64"
+
+ path_harfbuzz_build="$path_harfbuzz/build"
+ echo $path_harfbuzz_build
+
+ # grab the actual repo
+ clone_gitrepo "$path_harfbuzz" "$url_harfbuzz"
+
+ verify_path "$path_harfbuzz_build"
+
+ library_type="shared"
+ build_type="release"
+
+ pushd "$path_harfbuzz" > /dev/null
+
+ # Determine the latest C++ standard supported by the compiler
+ latest_cpp_standard=$(clang++ -dM -E - < /dev/null | grep __cplusplus | awk '{print $3}')
+ case $latest_cpp_standard in
+ 201703L) cpp_flag="-std=c++17" ;;
+ 202002L) cpp_flag="-std=c++20" ;;
+ 202302L) cpp_flag="-std=c++23" ;;
+ *) cpp_flag="-std=c++14" ;; # Default to C++14 if unable to determine
+ esac
+ echo "Using C++ standard: $cpp_flag"
+
+ compiler_args=(
+ "$cpp_flag"
+ "-Wall"
+ "-Wextra"
+ "-D_REENTRANT"
+ "-DHAVE_FALLBACK=1"
+ "-DHAVE_OT=1"
+ "-DHAVE_SUBSET=1"
+ "-DHB_USE_INTERNAL_PARSER"
+ "-DHB_NO_COLOR"
+ "-DHB_NO_DRAW"
+ "-DHB_NO_PARSE"
+ "-DHB_NO_MT"
+ "-DHB_NO_GRAPHITE2"
+ "-DHB_NO_ICU"
+ "-DHB_NO_DIRECTWRITE"
+ "-I$path_harfbuzz/src"
+ "-I$path_harfbuzz"
+ )
+
+ if [ "$library_type" = "shared" ]; then
+ compiler_args+=("-fPIC")
+ compiler_args+=("-DHAVE_DECLSPEC")
+ compiler_args+=("-DHARFBUZZ_EXPORTS")
+ fi
+
+ if [ "$build_type" = "debug" ]; then
+ compiler_args+=("-g" "-O0")
+ else
+ compiler_args+=("-O2")
+ fi
+
+ compiler_args_str="${compiler_args[*]}"
+
+ # Create config.h
+ cat > "$path_harfbuzz/config.h" << EOL
+#define HB_VERSION_MAJOR 9
+#define HB_VERSION_MINOR 0
+#define HB_VERSION_MICRO 0
+#define HB_VERSION_STRING "9.0.0"
+#define HAVE_ROUND 1
+#define HB_NO_BITMAP 1
+#define HB_NO_CFF 1
+#define HB_NO_OT_FONT_CFF 1
+#define HB_NO_SUBSET_CFF 1
+#define HB_HAVE_SUBSET 0
+#define HB_HAVE_OT 0
+#define HB_USER_DATA_KEY_DEFINE1(_name) extern HB_EXTERN hb_user_data_key_t _name
+EOL
+
+ # Create unity build file
+ cat > "$path_harfbuzz_build/harfbuzz_unity.cc" << EOL
+#define HB_EXTERN __attribute__((visibility("default")))
+
+// base
+#include "config.h"
+#include "hb-aat-layout.cc"
+#include "hb-aat-map.cc"
+#include "hb-blob.cc"
+#include "hb-buffer-serialize.cc"
+#include "hb-buffer-verify.cc"
+#include "hb-buffer.cc"
+#include "hb-common.cc"
+#include "hb-face.cc"
+#include "hb-face-builder.cc"
+#include "hb-fallback-shape.cc"
+#include "hb-font.cc"
+#include "hb-map.cc"
+#include "hb-number.cc"
+#include "hb-ot-cff1-table.cc"
+#include "hb-ot-cff2-table.cc"
+#include "hb-ot-color.cc"
+#include "hb-ot-face.cc"
+#include "hb-ot-font.cc"
+#include "hb-outline.cc"
+#include "OT/Var/VARC/VARC.cc"
+#include "hb-ot-layout.cc"
+#include "hb-ot-map.cc"
+#include "hb-ot-math.cc"
+#include "hb-ot-meta.cc"
+#include "hb-ot-metrics.cc"
+#include "hb-ot-name.cc"
+#include "hb-ot-shaper-arabic.cc"
+#include "hb-ot-shaper-default.cc"
+#include "hb-ot-shaper-hangul.cc"
+#include "hb-ot-shaper-hebrew.cc"
+#include "hb-ot-shaper-indic-table.cc"
+#include "hb-ot-shaper-indic.cc"
+#include "hb-ot-shaper-khmer.cc"
+#include "hb-ot-shaper-myanmar.cc"
+#include "hb-ot-shaper-syllabic.cc"
+#include "hb-ot-shaper-thai.cc"
+#include "hb-ot-shaper-use.cc"
+#include "hb-ot-shaper-vowel-constraints.cc"
+#include "hb-ot-shape-fallback.cc"
+#include "hb-ot-shape-normalize.cc"
+#include "hb-ot-shape.cc"
+#include "hb-ot-tag.cc"
+#include "hb-ot-var.cc"
+#include "hb-set.cc"
+#include "hb-shape-plan.cc"
+#include "hb-shape.cc"
+#include "hb-shaper.cc"
+#include "hb-static.cc"
+#include "hb-style.cc"
+#include "hb-ucd.cc"
+#include "hb-unicode.cc"
+EOL
+
+ # Compile unity file
+ pushd "$path_harfbuzz_build" > /dev/null
+ g++ $compiler_args_str -c harfbuzz_unity.cc -o harfbuzz_unity.o
+
+ if [ $? -ne 0 ]; then
+ echo "Compilation failed for unity build"
+ popd > /dev/null
+ popd > /dev/null
+ return 1
+ fi
+
+ # Create library
+ if [ "$library_type" = "static" ]; then
+ ar rcs libharfbuzz.a harfbuzz_unity.o
+ if [ $? -ne 0 ]; then
+ echo "Static library creation failed"
+ popd > /dev/null
+ popd > /dev/null
+ return 1
+ fi
+ output_file="libharfbuzz.a"
+ else
+ g++ -shared -o libharfbuzz.so harfbuzz_unity.o
+ if [ $? -ne 0 ]; then
+ echo "Shared library creation failed"
+ popd > /dev/null
+ popd > /dev/null
+ return 1
+ fi
+ output_file="libharfbuzz.so"
+ fi
+
+ popd > /dev/null # path_harfbuzz_build
+ popd > /dev/null # path_harfbuzz
+
+ # Copy files
+ cp "$path_harfbuzz_build/$output_file" "$path_linux64/"
+ if [ "$library_type" = "shared" ]; then
+ if [ -f "$path_harfbuzz_build/libharfbuzz.so" ]; then
+ cp "$path_harfbuzz_build/libharfbuzz.so" "$path_linux64/"
+ fi
+ fi
+
+ echo "Build completed and files copied to $path_linux64"
+}
+
+# Uncomment the function you want to use
+# build_repo
+build_repo_without_meson
diff --git a/thirdparty/harfbuzz/scripts/helpers/devshell.ps1 b/thirdparty/harfbuzz/scripts/helpers/devshell.ps1
new file mode 100644
index 0000000..33ca0ce
--- /dev/null
+++ b/thirdparty/harfbuzz/scripts/helpers/devshell.ps1
@@ -0,0 +1,28 @@
+if ($env:VCINSTALLDIR) {
+ return
+}
+
+$ErrorActionPreference = "Stop"
+
+# Use vswhere to find the latest Visual Studio installation
+$vswhere_out = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath
+if ($null -eq $vswhere_out) {
+ Write-Host "ERROR: Visual Studio installation not found"
+ exit 1
+}
+
+# Find Launch-VsDevShell.ps1 in the Visual Studio installation
+$vs_path = $vswhere_out
+$vs_devshell = Join-Path $vs_path "\Common7\Tools\Launch-VsDevShell.ps1"
+
+if ( -not (Test-Path $vs_devshell) ) {
+ Write-Host "ERROR: Launch-VsDevShell.ps1 not found in Visual Studio installation"
+ Write-Host Tested path: $vs_devshell
+ exit 1
+}
+
+# Launch the Visual Studio Developer Shell
+Push-Location
+write-host @args
+& $vs_devshell @args
+Pop-Location
diff --git a/thirdparty/harfbuzz/scripts/helpers/misc.ps1 b/thirdparty/harfbuzz/scripts/helpers/misc.ps1
new file mode 100644
index 0000000..e6b9456
--- /dev/null
+++ b/thirdparty/harfbuzz/scripts/helpers/misc.ps1
@@ -0,0 +1,72 @@
+function Clone-Gitrepo { param( [string] $path, [string] $url )
+ if (test-path $path) {
+ # git -C $path pull
+ }
+ else {
+ Write-Host "Cloning $url ..."
+ git clone $url $path
+ }
+}
+
+function Grab-Zip { param( $url, $path_file, $path_dst )
+ Invoke-WebRequest -Uri $url -OutFile $path_file
+ Expand-Archive -Path $path_file -DestinationPath $path_dst -Force
+}
+
+function Update-GitRepo
+{
+ param( [string] $path, [string] $url, [string] $build_command )
+
+ if ( $build_command -eq $null ) {
+ write-host "Attempted to call Update-GitRepo without build_command specified"
+ return
+ }
+
+ $repo_name = $url.Split('/')[-1].Replace('.git', '')
+
+ $last_built_commit = join-path $path_build "last_built_commit_$repo_name.txt"
+ if ( -not(test-path -Path $path))
+ {
+ write-host "Cloining repo from $url to $path"
+ git clone $url $path
+
+ write-host "Building $url"
+ push-location $path
+ & "$build_command"
+ pop-location
+
+ git -C $path rev-parse HEAD | out-file $last_built_commit
+ $script:binaries_dirty = $true
+ write-host
+ return
+ }
+
+ git -C $path fetch
+ $latest_commit_hash = git -C $path rev-parse '@{u}'
+ $last_built_hash = if (Test-Path $last_built_commit) { Get-Content $last_built_commit } else { "" }
+
+ if ( $latest_commit_hash -eq $last_built_hash ) {
+ write-host
+ return
+ }
+
+ write-host "Build out of date for: $path, updating"
+ write-host 'Pulling...'
+ git -C $path pull
+
+ write-host "Building $url"
+ push-location $path
+ & $build_command
+ pop-location
+
+ $latest_commit_hash | out-file $last_built_commit
+ $script:binaries_dirty = $true
+ write-host
+}
+
+function Verify-Path { param( $path )
+ if (test-path $path) {return $true}
+
+ new-item -ItemType Directory -Path $path
+ return $false
+}
diff --git a/thirdparty/harfbuzz/scripts/helpers/misc.sh b/thirdparty/harfbuzz/scripts/helpers/misc.sh
new file mode 100644
index 0000000..9be2127
--- /dev/null
+++ b/thirdparty/harfbuzz/scripts/helpers/misc.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+
+clone_gitrepo() {
+ local path="$1"
+ local url="$2"
+
+ if [ -d "$path" ]; then
+ # git -C "$path" pull
+ :
+ else
+ echo "Cloning $url ..."
+ git clone "$url" "$path"
+ fi
+}
+
+get_ini_content() {
+ local path_file="$1"
+ declare -A ini
+
+ local current_section=""
+ while IFS= read -r line; do
+ if [[ $line =~ ^\[(.+)\]$ ]]; then
+ current_section="${BASH_REMATCH[1]}"
+ ini["$current_section"]=""
+ elif [[ $line =~ ^([^=]+)=(.*)$ ]]; then
+ local key="${BASH_REMATCH[1]}"
+ local value="${BASH_REMATCH[2]}"
+ if [ -n "$current_section" ]; then
+ ini["$current_section,$key"]="$value"
+ fi
+ fi
+ done < "$path_file"
+
+ # To use this function, you would need to pass the result by reference
+ # and then access it in the calling function
+}
+
+invoke_with_color_coded_output() {
+ local command="$1"
+ eval "$command" 2>&1 | while IFS= read -r line; do
+ if [[ "$line" =~ [Ee]rror ]]; then
+ echo -e "\033[0;31m\t$line\033[0m" # Red for errors
+ elif [[ "$line" =~ [Ww]arning ]]; then
+ echo -e "\033[0;33m\t$line\033[0m" # Yellow for warnings
+ else
+ echo -e "\033[0;37m\t$line\033[0m" # White for other output
+ fi
+ done
+}
+
+update_git_repo() {
+ local path="$1"
+ local url="$2"
+ local build_command="$3"
+
+ if [ -z "$build_command" ]; then
+ echo "Attempted to call update_git_repo without build_command specified"
+ return
+ fi
+
+ local repo_name=$(basename "$url" .git)
+
+ local last_built_commit="$path_build/last_built_commit_$repo_name.txt"
+ if [ ! -d "$path" ]; then
+ echo "Cloning repo from $url to $path"
+ git clone "$url" "$path"
+
+ echo "Building $url"
+ pushd "$path" > /dev/null
+ eval "$build_command"
+ popd > /dev/null
+
+ git -C "$path" rev-parse HEAD > "$last_built_commit"
+ binaries_dirty=true
+ echo
+ return
+ fi
+
+ git -C "$path" fetch
+ local latest_commit_hash=$(git -C "$path" rev-parse '@{u}')
+ local last_built_hash=""
+ [ -f "$last_built_commit" ] && last_built_hash=$(cat "$last_built_commit")
+
+ if [ "$latest_commit_hash" = "$last_built_hash" ]; then
+ echo
+ return
+ fi
+
+ echo "Build out of date for: $path, updating"
+ echo 'Pulling...'
+ git -C "$path" pull
+
+ echo "Building $url"
+ pushd "$path" > /dev/null
+ eval "$build_command"
+ popd > /dev/null
+
+ echo "$latest_commit_hash" > "$last_built_commit"
+ binaries_dirty=true
+ echo
+}
+
+verify_path() {
+ local path="$1"
+ if [ -d "$path" ]; then
+ return 0
+ fi
+
+ mkdir -p "$path"
+ return 1
+}
\ No newline at end of file
diff --git a/thirdparty/sokol-tools/.gitignore b/thirdparty/sokol-tools/.gitignore
new file mode 100644
index 0000000..bfe2935
--- /dev/null
+++ b/thirdparty/sokol-tools/.gitignore
@@ -0,0 +1,4 @@
+.vscode/
+.zig-cache/
+zig-out/
+*.pyc
diff --git a/thirdparty/sokol-tools/LICENSE b/thirdparty/sokol-tools/LICENSE
new file mode 100644
index 0000000..396f640
--- /dev/null
+++ b/thirdparty/sokol-tools/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Andre Weissflog
+
+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.
diff --git a/thirdparty/sokol-tools/README.md b/thirdparty/sokol-tools/README.md
new file mode 100644
index 0000000..4128d4d
--- /dev/null
+++ b/thirdparty/sokol-tools/README.md
@@ -0,0 +1,2 @@
+# sokol-tools-bin
+Binaries and fips integration for https://github.com/floooh/sokol-tools
diff --git a/thirdparty/sokol-tools/bin/linux/readme.txt b/thirdparty/sokol-tools/bin/linux/readme.txt
new file mode 100644
index 0000000..c315007
--- /dev/null
+++ b/thirdparty/sokol-tools/bin/linux/readme.txt
@@ -0,0 +1,2 @@
+Precompiled binaries for Linux go here (64-bit statically linked).
+
diff --git a/thirdparty/sokol-tools/bin/linux/sokol-shdc b/thirdparty/sokol-tools/bin/linux/sokol-shdc
new file mode 100644
index 0000000..2ce2e7e
Binary files /dev/null and b/thirdparty/sokol-tools/bin/linux/sokol-shdc differ
diff --git a/thirdparty/sokol-tools/bin/linux_arm64/sokol-shdc b/thirdparty/sokol-tools/bin/linux_arm64/sokol-shdc
new file mode 100644
index 0000000..7dd984a
Binary files /dev/null and b/thirdparty/sokol-tools/bin/linux_arm64/sokol-shdc differ
diff --git a/thirdparty/sokol-tools/bin/osx/readme.txt b/thirdparty/sokol-tools/bin/osx/readme.txt
new file mode 100644
index 0000000..ff18fea
--- /dev/null
+++ b/thirdparty/sokol-tools/bin/osx/readme.txt
@@ -0,0 +1,2 @@
+Precompiled binaries for macOS go here.
+
diff --git a/thirdparty/sokol-tools/bin/osx/sokol-shdc b/thirdparty/sokol-tools/bin/osx/sokol-shdc
new file mode 100644
index 0000000..2e9b81f
Binary files /dev/null and b/thirdparty/sokol-tools/bin/osx/sokol-shdc differ
diff --git a/thirdparty/sokol-tools/bin/osx_arm64/sokol-shdc b/thirdparty/sokol-tools/bin/osx_arm64/sokol-shdc
new file mode 100644
index 0000000..5372830
Binary files /dev/null and b/thirdparty/sokol-tools/bin/osx_arm64/sokol-shdc differ
diff --git a/thirdparty/sokol-tools/bin/win32/readme.txt b/thirdparty/sokol-tools/bin/win32/readme.txt
new file mode 100644
index 0000000..f0ec175
--- /dev/null
+++ b/thirdparty/sokol-tools/bin/win32/readme.txt
@@ -0,0 +1,2 @@
+Precompiled binaries for Windows (64-bit) go here.
+
diff --git a/thirdparty/sokol-tools/bin/win32/sokol-shdc.exe b/thirdparty/sokol-tools/bin/win32/sokol-shdc.exe
new file mode 100644
index 0000000..140a187
Binary files /dev/null and b/thirdparty/sokol-tools/bin/win32/sokol-shdc.exe differ
diff --git a/thirdparty/sokol-tools/build.zig b/thirdparty/sokol-tools/build.zig
new file mode 100644
index 0000000..06f3bf8
--- /dev/null
+++ b/thirdparty/sokol-tools/build.zig
@@ -0,0 +1,143 @@
+//! Helper code to invoke sokol-shdc from the Zig build system.
+//! See https://github.com/floooh/sokol-zig for an example
+//! of how to use sokol-tools-bin as a lazy dependency and
+//! compile shaders (search for `shdc.compile` in the sokol-zig build.zig)
+const std = @import("std");
+const builtin = @import("builtin");
+const Allocator = std.mem.Allocator;
+const Build = std.Build;
+
+pub const Options = struct {
+ dep_shdc: *Build.Dependency,
+ input: Build.LazyPath,
+ output: Build.LazyPath,
+ slang: Slang,
+ format: Format = .sokol_zig,
+ tmp_dir: ?Build.LazyPath = null,
+ defines: ?[][]const u8 = null,
+ module: ?[]const u8 = null,
+ reflection: bool = false,
+ bytecode: bool = false,
+ dump: bool = false,
+ genver: ?[]const u8 = null,
+ ifdef: bool = false,
+ noifdef: bool = false,
+ save_intermediate_spirv: bool = false,
+ no_log_cmdline: bool = true,
+};
+
+pub fn compile(b: *Build, opts: Options) !*Build.Step.Run {
+ const shdc_path = try getShdcLazyPath(opts.dep_shdc);
+ const args = try optsToArgs(opts, b, shdc_path);
+ var step = b.addSystemCommand(args);
+ step.addFileArg(opts.input);
+ return step;
+}
+
+/// target shader languages
+/// NOTE: make sure that field names match the cmdline arg string
+pub const Slang = packed struct(u10) {
+ glsl410: bool = false,
+ glsl430: bool = false,
+ glsl300es: bool = false,
+ glsl310es: bool = false,
+ hlsl4: bool = false,
+ hlsl5: bool = false,
+ metal_macos: bool = false,
+ metal_ios: bool = false,
+ metal_sim: bool = false,
+ wgsl: bool = false,
+};
+
+fn slangToString(slang: Slang, a: Allocator) ![]const u8 {
+ var strings: [16][]const u8 = undefined;
+ var num_strings: usize = 0;
+ inline for (std.meta.fields(Slang)) |field| {
+ if (@field(slang, field.name)) {
+ strings[num_strings] = field.name;
+ num_strings += 1;
+ }
+ }
+ return std.mem.join(a, ":", strings[0..num_strings]);
+}
+
+/// the code-generation target language
+/// NOTE: make sure that the item names match the cmdline arg string
+pub const Format = enum {
+ sokol,
+ sokol_impl,
+ sokol_zig,
+ sokol_nim,
+ sokol_odin,
+ sokol_rust,
+ sokol_d,
+ sokol_jai,
+};
+
+fn formatToString(f: Format) []const u8 {
+ return @tagName(f);
+}
+
+fn getShdcLazyPath(dep_shdc: *Build.Dependency) !Build.LazyPath {
+ const intel = builtin.cpu.arch.isX86();
+ const opt_sub_path: ?[]const u8 = switch (builtin.os.tag) {
+ .windows => "bin/win32/sokol-shdc.exe",
+ .linux => if (intel) "bin/linux/sokol-shdc" else "bin/linux_arm64/sokol-shdc",
+ .macos => if (intel) "bin/osx/sokol-shdc" else "bin/osx_arm64/sokol-shdc",
+ else => null,
+ };
+ if (opt_sub_path) |sub_path| {
+ return dep_shdc.path(sub_path);
+ } else {
+ return error.ShdcUnsupportedPlatform;
+ }
+}
+
+fn optsToArgs(opts: Options, b: *Build, tool_path: Build.LazyPath) ![]const []const u8 {
+ const a = b.allocator;
+ var arr: std.ArrayListUnmanaged([]const u8) = .empty;
+ try arr.append(a, tool_path.getPath(b));
+ try arr.appendSlice(a, &.{ "-o", opts.output.getPath(b) });
+ try arr.appendSlice(a, &.{ "-l", try slangToString(opts.slang, a) });
+ try arr.appendSlice(a, &.{ "-f", formatToString(opts.format) });
+ if (opts.tmp_dir) |tmp_dir| {
+ try arr.appendSlice(a, &.{ "--tmpdir", tmp_dir.getPath(b) });
+ }
+ if (opts.defines) |defines| {
+ try arr.appendSlice(a, &.{ "--defines", try std.mem.join(a, ":", defines) });
+ }
+ if (opts.module) |module| {
+ try arr.appendSlice(a, &.{ "--module", b.dupe(module) });
+ }
+ if (opts.reflection) {
+ try arr.append(a, "--reflection");
+ }
+ if (opts.bytecode) {
+ try arr.append(a, "--bytecode");
+ }
+ if (opts.dump) {
+ try arr.append(a, "--dump");
+ }
+ if (opts.genver) |genver| {
+ try arr.appendSlice(a, &.{ "--genver", b.dupe(genver) });
+ }
+ if (opts.ifdef) {
+ try arr.append(a, "--ifdef");
+ }
+ if (opts.noifdef) {
+ try arr.append(a, "--noifdef");
+ }
+ if (opts.save_intermediate_spirv) {
+ try arr.append(a, "--save-intermediate-spirv");
+ }
+ if (opts.no_log_cmdline) {
+ try arr.append(a, "--no-log-cmdline");
+ }
+ // important: keep this last
+ try arr.append(a, "-i");
+ return arr.toOwnedSlice(a);
+}
+
+pub fn build(b: *Build) void {
+ _ = b;
+}
diff --git a/thirdparty/sokol-tools/build.zig.zon b/thirdparty/sokol-tools/build.zig.zon
new file mode 100644
index 0000000..ced1d9e
--- /dev/null
+++ b/thirdparty/sokol-tools/build.zig.zon
@@ -0,0 +1,7 @@
+.{
+ .name = .sokolshdc,
+ .fingerprint = 0xe0596dde0e9962af,
+ .version = "0.1.0",
+ .minimum_zig_version = "0.14.0",
+ .paths = .{ "bin", "build.zig", "build.zig.zon" },
+}
diff --git a/thirdparty/sokol-tools/fips-files/generators/SokolShader.py b/thirdparty/sokol-tools/fips-files/generators/SokolShader.py
new file mode 100644
index 0000000..2f29dfd
--- /dev/null
+++ b/thirdparty/sokol-tools/fips-files/generators/SokolShader.py
@@ -0,0 +1,62 @@
+#-------------------------------------------------------------------------------
+# SokolShader.py
+#
+# Fips code-generator script for invoking sokol-shdc during the build.
+#
+# Use the cmake macro 'sokol_shader([glsl-file] [shader-dialects])' inside a
+# fips target (fips_begin_* / fips_end_*) to hook the code-generation
+# build job into the build process.
+#-------------------------------------------------------------------------------
+
+Version = 5
+
+import os, platform, subprocess
+import genutil as util
+from mod import log
+
+#-------------------------------------------------------------------------------
+def find_shdc():
+ shdc_path = os.path.dirname(os.path.abspath(__file__))
+ shdc_path += '/../../bin/'
+ if platform.system() == 'Windows':
+ shdc_path += 'win32/'
+ elif platform.system() == 'Darwin':
+ if platform.machine() == 'arm64':
+ shdc_path += 'osx_arm64/'
+ else:
+ shdc_path += 'osx/'
+ elif platform.system() == 'Linux':
+ if platform.machine() in ['aarch64', 'arm64']:
+ shdc_path += 'linux_arm64/'
+ else:
+ shdc_path += 'linux/'
+ else:
+ log.error('Unknown host system {}'.format(platform.system()))
+ return shdc_path + 'sokol-shdc'
+
+#-------------------------------------------------------------------------------
+def generate(input, out_src, out_hdr, args):
+ errfmt = 'msvc' if args['compiler']=='MSVC' else 'gcc'
+ if util.isDirty(Version, [input], [out_hdr]):
+ print('## sokol-shdc: {} {} {}'.format(input, out_hdr, str(args)))
+ cmd = [find_shdc(),
+ '--input', input,
+ '--output', out_hdr,
+ '--slang', args['slang'],
+ '--genver', str(Version),
+ '--errfmt', errfmt,
+ '--format', 'sokol']
+ if 'defines' in args:
+ cmd.extend(['--defines', args['defines']])
+ if 'module' in args:
+ cmd.extend(['--module', args['module']])
+ if 'reflection' in args:
+ if args['reflection']:
+ cmd.extend(['--reflection'])
+ if 'debuggable' in args and args['debuggable']:
+ pass
+ else:
+ cmd.extend(['--bytecode'])
+ res = subprocess.call(cmd)
+ if res != 0:
+ log.error('sokol-shdc returned with error code {}'.format(res))
diff --git a/thirdparty/sokol-tools/fips-files/include.cmake b/thirdparty/sokol-tools/fips-files/include.cmake
new file mode 100644
index 0000000..9a7879d
--- /dev/null
+++ b/thirdparty/sokol-tools/fips-files/include.cmake
@@ -0,0 +1,25 @@
+macro(sokol_shader shd slang)
+ set(args "{slang: '${slang}', compiler: '${CMAKE_C_COMPILER_ID}' }")
+ fips_generate(TYPE SokolShader FROM ${shd} HEADER ${shd}.h OUT_OF_SOURCE ARGS ${args})
+endmacro()
+
+# special version which doesn't generate binary output, this allows shaders to be debugged
+macro(sokol_shader_debuggable shd slang)
+ set(args "{slang: '${slang}', compiler: '${CMAKE_C_COMPILER_ID}', debuggable: true }")
+ fips_generate(TYPE SokolShader FROM ${shd} HEADER ${shd}.h OUT_OF_SOURCE ARGS ${args})
+endmacro()
+
+macro(sokol_shader_variant shd slang module defines)
+ set(args "{slang: '${slang}', compiler: '${CMAKE_C_COMPILER_ID}', defines: '${defines}', module: '${module}' }")
+ fips_generate(TYPE SokolShader FROM ${shd} HEADER ${shd}.${module}.h OUT_OF_SOURCE ARGS ${args})
+endmacro()
+
+macro(sokol_shader_with_reflection shd slang)
+ set(args "{slang: '${slang}', compiler: '${CMAKE_C_COMPILER_ID}', reflection: true }")
+ fips_generate(TYPE SokolShader FROM ${shd} HEADER ${shd}.h OUT_OF_SOURCE ARGS ${args})
+endmacro()
+
+macro(sokol_shader_variant_with_reflection shd slang module defines)
+ set(args "{slang: '${slang}', compiler: '${CMAKE_C_COMPILER_ID}', defines: '${defines}', module: '${module}', reflection: true }")
+ fips_generate(TYPE SokolShader FROM ${shd} HEADER ${shd}.${module}.h OUT_OF_SOURCE ARGS ${args})
+endmacro()
diff --git a/thirdparty/sokol/.gitignore b/thirdparty/sokol/.gitignore
new file mode 100644
index 0000000..ce463e9
--- /dev/null
+++ b/thirdparty/sokol/.gitignore
@@ -0,0 +1,15 @@
+.vscode/
+build/
+*.a
+*.o
+*.lib
+*.obj
+*.pdb
+*.bin
+*.exp
+*.ilk
+*.dll
+*.dylib
+*.exe
+*.dSYM/
+ols.json
diff --git a/thirdparty/sokol/CHANGELOG.md b/thirdparty/sokol/CHANGELOG.md
new file mode 100644
index 0000000..cfb4772
--- /dev/null
+++ b/thirdparty/sokol/CHANGELOG.md
@@ -0,0 +1,12 @@
+## CHANGELOG
+
+> NOTE: this changelog is only for changes to the sokol-odin 'scaffolding'.
+For actual Sokol header changes, see the
+[sokol changelog](https://github.com/floooh/sokol/blob/master/CHANGELOG.md).
+
+### 13-Apr-2024
+
+Merged PR https://github.com/floooh/sokol-odin/pull/11, this changes the
+directory structure of the bindings repository, adds support to build
+the Windows bindings as a DLL and a couple of smaller things
+detailed here: https://github.com/floooh/sokol/pull/1023
diff --git a/thirdparty/sokol/LICENSE b/thirdparty/sokol/LICENSE
new file mode 100644
index 0000000..05a72ef
--- /dev/null
+++ b/thirdparty/sokol/LICENSE
@@ -0,0 +1,22 @@
+zlib/libpng license
+
+Copyright (c) 2022 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.
diff --git a/thirdparty/sokol/README.md b/thirdparty/sokol/README.md
new file mode 100644
index 0000000..02b1154
--- /dev/null
+++ b/thirdparty/sokol/README.md
@@ -0,0 +1,81 @@
+[](https://github.com/floooh/sokol-odin/actions/workflows/main.yml)
+
+Auto-generated [Odin](https://github.com/odin-lang/odin) 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:
+
+ ```
+ 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:
+ ```
+ mkdir build
+ cd build
+ ```
+
+3. Build and run the samples:
+ ```
+ odin run ../examples/clear -debug
+ odin run ../examples/triangle -debug
+ odin run ../examples/quad -debug
+ odin run ../examples/bufferoffsets -debug
+ odin run ../examples/cube -debug
+ odin run ../examples/noninterleaved -debug
+ odin run ../examples/texcube -debug
+ odin run ../examples/shapes -debug
+ odin run ../examples/offscreen -debug
+ odin run ../examples/instancing -debug
+ odin run ../examples/mrt -debug
+ odin run ../examples/blend -debug
+ odin run ../examples/debugtext -debug
+ odin run ../examples/debugtext-print -debug
+ odin run ../examples/debugtext-userfont -debug
+ odin run ../examples/saudio -debug
+ odin run ../examples/sgl -debug
+ odin run ../examples/sgl-points -debug
+ odin run ../examples/sgl-context -debug
+ odin run ../examples/vertexpull -debug
+ ```
+
+ 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 ```-define:SOKOL_USE_GL=true```:
+
+ ```
+ odin run ../examples/clear -debug -define:SOKOL_USE_GL=true
+ ```
+
+ The ```clear``` sample prints the selected backend to the terminal:
+
+ ```
+ odin run ../examples/clear -debug -define:SOKOL_USE_GL=true
+ >> using GL backend
+ ```
+
+ On Windows, you can get rid of the automatically opened terminal window
+ by building with the ```-subsystem:windows``` option:
+
+ ```
+ odin build ../examples/clear -subsystem:windows
+ ```
diff --git a/thirdparty/sokol/app/app.odin b/thirdparty/sokol/app/app.odin
new file mode 100644
index 0000000..f1d3d32
--- /dev/null
+++ b/thirdparty/sokol/app/app.odin
@@ -0,0 +1,2033 @@
+// machine generated, do not edit
+
+package sokol_app
+
+/*
+
+ 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.
+
+*/
+
+import "core:c"
+
+_ :: c
+
+SOKOL_DEBUG :: #config(SOKOL_DEBUG, ODIN_DEBUG)
+
+DEBUG :: #config(SOKOL_APP_DEBUG, SOKOL_DEBUG)
+USE_GL :: #config(SOKOL_USE_GL, false)
+USE_DLL :: #config(SOKOL_DLL, false)
+
+when ODIN_OS == .Windows {
+ when USE_DLL {
+ when USE_GL {
+ when DEBUG { foreign import sokol_app_clib { "../sokol_dll_windows_x64_gl_debug.lib" } }
+ else { foreign import sokol_app_clib { "../sokol_dll_windows_x64_gl_release.lib" } }
+ } else {
+ when DEBUG { foreign import sokol_app_clib { "../sokol_dll_windows_x64_d3d11_debug.lib" } }
+ else { foreign import sokol_app_clib { "../sokol_dll_windows_x64_d3d11_release.lib" } }
+ }
+ } else {
+ when USE_GL {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_windows_x64_gl_debug.lib" } }
+ else { foreign import sokol_app_clib { "sokol_app_windows_x64_gl_release.lib" } }
+ } else {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_windows_x64_d3d11_debug.lib" } }
+ else { foreign import sokol_app_clib { "sokol_app_windows_x64_d3d11_release.lib" } }
+ }
+ }
+} else when ODIN_OS == .Darwin {
+ when USE_DLL {
+ when USE_GL && ODIN_ARCH == .arm64 && DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib" } }
+ else when USE_GL && ODIN_ARCH == .arm64 && !DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_arm64_gl_release.dylib" } }
+ else when USE_GL && ODIN_ARCH == .amd64 && DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_x64_gl_debug.dylib" } }
+ else when USE_GL && ODIN_ARCH == .amd64 && !DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_x64_gl_release.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .arm64 && DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .arm64 && !DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_arm64_metal_release.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .amd64 && DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_x64_metal_debug.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .amd64 && !DEBUG { foreign import sokol_app_clib { "../dylib/sokol_dylib_macos_x64_metal_release.dylib" } }
+ } else {
+ when USE_GL {
+ when ODIN_ARCH == .arm64 {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_macos_arm64_gl_debug.a", "system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework" } }
+ else { foreign import sokol_app_clib { "sokol_app_macos_arm64_gl_release.a", "system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework" } }
+ } else {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_macos_x64_gl_debug.a", "system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework" } }
+ else { foreign import sokol_app_clib { "sokol_app_macos_x64_gl_release.a", "system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework" } }
+ }
+ } else {
+ when ODIN_ARCH == .arm64 {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_macos_arm64_metal_debug.a", "system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework" } }
+ else { foreign import sokol_app_clib { "sokol_app_macos_arm64_metal_release.a", "system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework" } }
+ } else {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_macos_x64_metal_debug.a", "system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework" } }
+ else { foreign import sokol_app_clib { "sokol_app_macos_x64_metal_release.a", "system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework" } }
+ }
+ }
+ }
+} else when ODIN_OS == .Linux {
+ when USE_DLL {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_linux_x64_gl_debug.so", "system:X11", "system:Xi", "system:Xcursor", "system:GL", "system:dl", "system:pthread" } }
+ else { foreign import sokol_app_clib { "sokol_app_linux_x64_gl_release.so", "system:X11", "system:Xi", "system:Xcursor", "system:GL", "system:dl", "system:pthread" } }
+ } else {
+ when DEBUG { foreign import sokol_app_clib { "sokol_app_linux_x64_gl_debug.a", "system:X11", "system:Xi", "system:Xcursor", "system:GL", "system:dl", "system:pthread" } }
+ else { foreign import sokol_app_clib { "sokol_app_linux_x64_gl_release.a", "system:X11", "system:Xi", "system:Xcursor", "system:GL", "system:dl", "system:pthread" } }
+ }
+} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+ // Feed sokol_app_wasm_gl_debug.a or sokol_app_wasm_gl_release.a into emscripten compiler.
+ foreign import sokol_app_clib { "env.o" }
+} else {
+ #panic("This OS is currently not supported")
+}
+
+@(default_calling_convention="c", link_prefix="sapp_")
+foreign sokol_app_clib {
+ // returns true after sokol-app has been initialized
+ isvalid :: proc() -> bool ---
+ // returns the current framebuffer width in pixels
+ width :: proc() -> c.int ---
+ // same as sapp_width(), but returns float
+ widthf :: proc() -> f32 ---
+ // returns the current framebuffer height in pixels
+ height :: proc() -> c.int ---
+ // same as sapp_height(), but returns float
+ heightf :: proc() -> f32 ---
+ // get default framebuffer color pixel format
+ color_format :: proc() -> c.int ---
+ // get default framebuffer depth pixel format
+ depth_format :: proc() -> c.int ---
+ // get default framebuffer sample count
+ sample_count :: proc() -> c.int ---
+ // returns true when high_dpi was requested and actually running in a high-dpi scenario
+ high_dpi :: proc() -> bool ---
+ // returns the dpi scaling factor (window pixels to framebuffer pixels)
+ dpi_scale :: proc() -> f32 ---
+ // show or hide the mobile device onscreen keyboard
+ show_keyboard :: proc(show: bool) ---
+ // return true if the mobile device onscreen keyboard is currently shown
+ keyboard_shown :: proc() -> bool ---
+ // query fullscreen mode
+ is_fullscreen :: proc() -> bool ---
+ // toggle fullscreen mode
+ toggle_fullscreen :: proc() ---
+ // show or hide the mouse cursor
+ show_mouse :: proc(show: bool) ---
+ // show or hide the mouse cursor
+ mouse_shown :: proc() -> bool ---
+ // enable/disable mouse-pointer-lock mode
+ lock_mouse :: proc(lock: bool) ---
+ // return true if in mouse-pointer-lock mode (this may toggle a few frames later)
+ mouse_locked :: proc() -> bool ---
+ // set mouse cursor type
+ set_mouse_cursor :: proc(cursor: Mouse_Cursor) ---
+ // get current mouse cursor type
+ get_mouse_cursor :: proc() -> Mouse_Cursor ---
+ // return the userdata pointer optionally provided in sapp_desc
+ userdata :: proc() -> rawptr ---
+ // return a copy of the sapp_desc structure
+ query_desc :: proc() -> Desc ---
+ // initiate a "soft quit" (sends SAPP_EVENTTYPE_QUIT_REQUESTED)
+ request_quit :: proc() ---
+ // cancel a pending quit (when SAPP_EVENTTYPE_QUIT_REQUESTED has been received)
+ cancel_quit :: proc() ---
+ // initiate a "hard quit" (quit application without sending SAPP_EVENTTYPE_QUIT_REQUESTED)
+ quit :: proc() ---
+ // call from inside event callback to consume the current event (don't forward to platform)
+ consume_event :: proc() ---
+ // get the current frame counter (for comparison with sapp_event.frame_count)
+ frame_count :: proc() -> u64 ---
+ // get an averaged/smoothed frame duration in seconds
+ frame_duration :: proc() -> f64 ---
+ // write string into clipboard
+ set_clipboard_string :: proc(str: cstring) ---
+ // read string from clipboard (usually during SAPP_EVENTTYPE_CLIPBOARD_PASTED)
+ get_clipboard_string :: proc() -> cstring ---
+ // set the window title (only on desktop platforms)
+ set_window_title :: proc(str: cstring) ---
+ // set the window icon (only on Windows and Linux)
+ set_icon :: proc(#by_ptr icon_desc: Icon_Desc) ---
+ // gets the total number of dropped files (after an SAPP_EVENTTYPE_FILES_DROPPED event)
+ get_num_dropped_files :: proc() -> c.int ---
+ // gets the dropped file paths
+ get_dropped_file_path :: proc(#any_int index: c.int) -> cstring ---
+ // special run-function for SOKOL_NO_ENTRY (in standard mode this is an empty stub)
+ run :: proc(#by_ptr desc: Desc) ---
+ // EGL: get EGLDisplay object
+ egl_get_display :: proc() -> rawptr ---
+ // EGL: get EGLContext object
+ egl_get_context :: proc() -> rawptr ---
+ // HTML5: enable or disable the hardwired "Leave Site?" dialog box
+ html5_ask_leave_site :: proc(ask: bool) ---
+ // HTML5: get byte size of a dropped file
+ html5_get_dropped_file_size :: proc(#any_int index: c.int) -> u32 ---
+ // HTML5: asynchronously load the content of a dropped file
+ html5_fetch_dropped_file :: proc(#by_ptr request: Html5_Fetch_Request) ---
+ // Metal: get bridged pointer to Metal device object
+ metal_get_device :: proc() -> rawptr ---
+ // Metal: get bridged pointer to MTKView's current drawable of type CAMetalDrawable
+ metal_get_current_drawable :: proc() -> rawptr ---
+ // Metal: get bridged pointer to MTKView's depth-stencil texture of type MTLTexture
+ metal_get_depth_stencil_texture :: proc() -> rawptr ---
+ // Metal: get bridged pointer to MTKView's msaa-color-texture of type MTLTexture (may be null)
+ metal_get_msaa_color_texture :: proc() -> rawptr ---
+ // macOS: get bridged pointer to macOS NSWindow
+ macos_get_window :: proc() -> rawptr ---
+ // iOS: get bridged pointer to iOS UIWindow
+ ios_get_window :: proc() -> rawptr ---
+ // D3D11: get pointer to ID3D11Device object
+ d3d11_get_device :: proc() -> rawptr ---
+ // D3D11: get pointer to ID3D11DeviceContext object
+ d3d11_get_device_context :: proc() -> rawptr ---
+ // D3D11: get pointer to IDXGISwapChain object
+ d3d11_get_swap_chain :: proc() -> rawptr ---
+ // D3D11: get pointer to ID3D11RenderTargetView object for rendering
+ d3d11_get_render_view :: proc() -> rawptr ---
+ // D3D11: get pointer ID3D11RenderTargetView object for msaa-resolve (may return null)
+ d3d11_get_resolve_view :: proc() -> rawptr ---
+ // D3D11: get pointer ID3D11DepthStencilView
+ d3d11_get_depth_stencil_view :: proc() -> rawptr ---
+ // Win32: get the HWND window handle
+ win32_get_hwnd :: proc() -> rawptr ---
+ // WebGPU: get WGPUDevice handle
+ wgpu_get_device :: proc() -> rawptr ---
+ // WebGPU: get swapchain's WGPUTextureView handle for rendering
+ wgpu_get_render_view :: proc() -> rawptr ---
+ // WebGPU: get swapchain's MSAA-resolve WGPUTextureView (may return null)
+ wgpu_get_resolve_view :: proc() -> rawptr ---
+ // WebGPU: get swapchain's WGPUTextureView for the depth-stencil surface
+ wgpu_get_depth_stencil_view :: proc() -> rawptr ---
+ // GL: get framebuffer object
+ gl_get_framebuffer :: proc() -> u32 ---
+ // GL: get major version
+ gl_get_major_version :: proc() -> c.int ---
+ // GL: get minor version
+ gl_get_minor_version :: proc() -> c.int ---
+ // GL: return true if the context is GLES
+ gl_is_gles :: proc() -> bool ---
+ // X11: get Window
+ x11_get_window :: proc() -> rawptr ---
+ // X11: get Display
+ x11_get_display :: proc() -> rawptr ---
+ // Android: get native activity handle
+ android_get_native_activity :: proc() -> rawptr ---
+}
+
+// misc constants
+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.
+*/
+Event_Type :: enum i32 {
+ 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.
+*/
+Keycode :: enum i32 {
+ 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
+*/
+Android_Tooltype :: enum i32 {
+ UNKNOWN = 0,
+ FINGER = 1,
+ STYLUS = 2,
+ MOUSE = 3,
+}
+
+/*
+ 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.
+*/
+Touchpoint :: struct {
+ identifier : c.uintptr_t,
+ pos_x : f32,
+ pos_y : f32,
+ android_tooltype : 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.
+*/
+Mousebutton :: enum i32 {
+ LEFT = 0,
+ RIGHT = 1,
+ MIDDLE = 2,
+ INVALID = 256,
+}
+
+/*
+ These are currently pressed modifier keys (and mouse buttons) which are
+ passed in the event struct field sapp_event.modifiers.
+*/
+MODIFIER_SHIFT :: 1
+MODIFIER_CTRL :: 2
+MODIFIER_ALT :: 4
+MODIFIER_SUPER :: 8
+MODIFIER_LMB :: 256
+MODIFIER_RMB :: 512
+MODIFIER_MMB :: 1024
+
+/*
+ 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.
+*/
+Event :: struct {
+ frame_count : u64,
+ type : Event_Type,
+ key_code : Keycode,
+ char_code : u32,
+ key_repeat : bool,
+ modifiers : u32,
+ mouse_button : Mousebutton,
+ mouse_x : f32,
+ mouse_y : f32,
+ mouse_dx : f32,
+ mouse_dy : f32,
+ scroll_x : f32,
+ scroll_y : f32,
+ num_touches : c.int,
+ touches : [8]Touchpoint,
+ window_width : c.int,
+ window_height : c.int,
+ framebuffer_width : c.int,
+ framebuffer_height : c.int,
+}
+
+/*
+ sg_range
+
+ A general pointer/size-pair struct and constructor macros for passing binary blobs
+ into sokol_app.h.
+*/
+Range :: struct {
+ ptr : rawptr,
+ size : c.size_t,
+}
+
+/*
+ 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
+*/
+Image_Desc :: struct {
+ width : c.int,
+ height : c.int,
+ pixels : Range,
+}
+
+/*
+ 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.
+*/
+Icon_Desc :: struct {
+ sokol_default : bool,
+ images : [8]Image_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).
+*/
+Allocator :: struct {
+ alloc_fn : proc "c" (a0: c.size_t, a1: rawptr) -> rawptr,
+ free_fn : proc "c" (a0: rawptr, a1: rawptr),
+ user_data : rawptr,
+}
+
+Log_Item :: enum i32 {
+ 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
+
+ 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).
+*/
+Logger :: struct {
+ func : proc "c" (a0: cstring, a1: u32, a2: u32, a3: cstring, a4: u32, a5: cstring, a6: rawptr),
+ user_data : rawptr,
+}
+
+/*
+ sokol-app initialization options, used as return value of sokol_main()
+ or sapp_run() argument.
+*/
+Desc :: struct {
+ init_cb : proc "c" (),
+ frame_cb : proc "c" (),
+ cleanup_cb : proc "c" (),
+ event_cb : proc "c" (a0: ^Event),
+ user_data : rawptr,
+ init_userdata_cb : proc "c" (a0: rawptr),
+ frame_userdata_cb : proc "c" (a0: rawptr),
+ cleanup_userdata_cb : proc "c" (a0: rawptr),
+ event_userdata_cb : proc "c" (a0: ^Event, a1: rawptr),
+ width : c.int,
+ height : c.int,
+ sample_count : c.int,
+ swap_interval : c.int,
+ high_dpi : bool,
+ fullscreen : bool,
+ alpha : bool,
+ window_title : cstring,
+ enable_clipboard : bool,
+ clipboard_size : c.int,
+ enable_dragndrop : bool,
+ max_dropped_files : c.int,
+ max_dropped_file_path_length : c.int,
+ icon : Icon_Desc,
+ allocator : Allocator,
+ logger : Logger,
+ gl_major_version : c.int,
+ gl_minor_version : c.int,
+ win32_console_utf8 : bool,
+ win32_console_create : bool,
+ win32_console_attach : bool,
+ html5_canvas_selector : cstring,
+ 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.
+*/
+Html5_Fetch_Error :: enum i32 {
+ FETCH_ERROR_NO_ERROR,
+ FETCH_ERROR_BUFFER_TOO_SMALL,
+ FETCH_ERROR_OTHER,
+}
+
+Html5_Fetch_Response :: struct {
+ succeeded : bool,
+ error_code : Html5_Fetch_Error,
+ file_index : c.int,
+ data : Range,
+ buffer : Range,
+ user_data : rawptr,
+}
+
+Html5_Fetch_Request :: struct {
+ dropped_file_index : c.int,
+ callback : proc "c" (a0: ^Html5_Fetch_Response),
+ buffer : Range,
+ user_data : rawptr,
+}
+
+/*
+ sapp_mouse_cursor
+
+ Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor)
+*/
+Mouse_Cursor :: enum i32 {
+ DEFAULT = 0,
+ ARROW,
+ IBEAM,
+ CROSSHAIR,
+ POINTING_HAND,
+ RESIZE_EW,
+ RESIZE_NS,
+ RESIZE_NWSE,
+ RESIZE_NESW,
+ RESIZE_ALL,
+ NOT_ALLOWED,
+}
+
diff --git a/thirdparty/sokol/audio/audio.odin b/thirdparty/sokol/audio/audio.odin
new file mode 100644
index 0000000..8ff0dd6
--- /dev/null
+++ b/thirdparty/sokol/audio/audio.odin
@@ -0,0 +1,649 @@
+// machine generated, do not edit
+
+package sokol_audio
+
+/*
+
+ 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.
+
+*/
+
+import "core:c"
+
+_ :: c
+
+SOKOL_DEBUG :: #config(SOKOL_DEBUG, ODIN_DEBUG)
+
+DEBUG :: #config(SOKOL_AUDIO_DEBUG, SOKOL_DEBUG)
+USE_GL :: #config(SOKOL_USE_GL, false)
+USE_DLL :: #config(SOKOL_DLL, false)
+
+when ODIN_OS == .Windows {
+ when USE_DLL {
+ when USE_GL {
+ when DEBUG { foreign import sokol_audio_clib { "../sokol_dll_windows_x64_gl_debug.lib" } }
+ else { foreign import sokol_audio_clib { "../sokol_dll_windows_x64_gl_release.lib" } }
+ } else {
+ when DEBUG { foreign import sokol_audio_clib { "../sokol_dll_windows_x64_d3d11_debug.lib" } }
+ else { foreign import sokol_audio_clib { "../sokol_dll_windows_x64_d3d11_release.lib" } }
+ }
+ } else {
+ when USE_GL {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_windows_x64_gl_debug.lib" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_windows_x64_gl_release.lib" } }
+ } else {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_windows_x64_d3d11_debug.lib" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_windows_x64_d3d11_release.lib" } }
+ }
+ }
+} else when ODIN_OS == .Darwin {
+ when USE_DLL {
+ when USE_GL && ODIN_ARCH == .arm64 && DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_arm64_gl_debug.dylib" } }
+ else when USE_GL && ODIN_ARCH == .arm64 && !DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_arm64_gl_release.dylib" } }
+ else when USE_GL && ODIN_ARCH == .amd64 && DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_x64_gl_debug.dylib" } }
+ else when USE_GL && ODIN_ARCH == .amd64 && !DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_x64_gl_release.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .arm64 && DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_arm64_metal_debug.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .arm64 && !DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_arm64_metal_release.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .amd64 && DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_x64_metal_debug.dylib" } }
+ else when !USE_GL && ODIN_ARCH == .amd64 && !DEBUG { foreign import sokol_audio_clib { "../dylib/sokol_dylib_macos_x64_metal_release.dylib" } }
+ } else {
+ when USE_GL {
+ when ODIN_ARCH == .arm64 {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_macos_arm64_gl_debug.a", "system:AudioToolbox.framework" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_macos_arm64_gl_release.a", "system:AudioToolbox.framework" } }
+ } else {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_macos_x64_gl_debug.a", "system:AudioToolbox.framework" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_macos_x64_gl_release.a", "system:AudioToolbox.framework" } }
+ }
+ } else {
+ when ODIN_ARCH == .arm64 {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_macos_arm64_metal_debug.a", "system:AudioToolbox.framework" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_macos_arm64_metal_release.a", "system:AudioToolbox.framework" } }
+ } else {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_macos_x64_metal_debug.a", "system:AudioToolbox.framework" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_macos_x64_metal_release.a", "system:AudioToolbox.framework" } }
+ }
+ }
+ }
+} else when ODIN_OS == .Linux {
+ when USE_DLL {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_linux_x64_gl_debug.so", "system:asound", "system:dl", "system:pthread" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_linux_x64_gl_release.so", "system:asound", "system:dl", "system:pthread" } }
+ } else {
+ when DEBUG { foreign import sokol_audio_clib { "sokol_audio_linux_x64_gl_debug.a", "system:asound", "system:dl", "system:pthread" } }
+ else { foreign import sokol_audio_clib { "sokol_audio_linux_x64_gl_release.a", "system:asound", "system:dl", "system:pthread" } }
+ }
+} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+ // Feed sokol_audio_wasm_gl_debug.a or sokol_audio_wasm_gl_release.a into emscripten compiler.
+ foreign import sokol_audio_clib { "env.o" }
+} else {
+ #panic("This OS is currently not supported")
+}
+
+@(default_calling_convention="c", link_prefix="saudio_")
+foreign sokol_audio_clib {
+ // setup sokol-audio
+ setup :: proc(#by_ptr desc: Desc) ---
+ // shutdown sokol-audio
+ shutdown :: proc() ---
+ // true after setup if audio backend was successfully initialized
+ isvalid :: proc() -> bool ---
+ // return the saudio_desc.user_data pointer
+ userdata :: proc() -> rawptr ---
+ // return a copy of the original saudio_desc struct
+ query_desc :: proc() -> Desc ---
+ // actual sample rate
+ sample_rate :: proc() -> c.int ---
+ // return actual backend buffer size in number of frames
+ buffer_frames :: proc() -> c.int ---
+ // actual number of channels
+ channels :: proc() -> c.int ---
+ // return true if audio context is currently suspended (only in WebAudio backend, all other backends return false)
+ suspended :: proc() -> bool ---
+ // get current number of frames to fill packet queue
+ expect :: proc() -> c.int ---
+ // push sample frames from main thread, returns number of frames actually pushed
+ push :: proc(frames: ^f32, #any_int num_frames: c.int) -> c.int ---
+}
+
+Log_Item :: enum i32 {
+ 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
+
+ Used in saudio_desc to provide a custom logging and error reporting
+ callback to sokol-audio.
+*/
+Logger :: struct {
+ func : proc "c" (a0: cstring, a1: u32, a2: u32, a3: cstring, a4: u32, a5: cstring, a6: rawptr),
+ user_data : rawptr,
+}
+
+/*
+ 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).
+*/
+Allocator :: struct {
+ alloc_fn : proc "c" (a0: c.size_t, a1: rawptr) -> rawptr,
+ free_fn : proc "c" (a0: rawptr, a1: rawptr),
+ user_data : rawptr,
+}
+
+Desc :: struct {
+ sample_rate : c.int,
+ num_channels : c.int,
+ buffer_frames : c.int,
+ packet_frames : c.int,
+ num_packets : c.int,
+ stream_cb : proc "c" (a0: ^f32, a1: c.int, a2: c.int),
+ stream_userdata_cb : proc "c" (a0: ^f32, a1: c.int, a2: c.int, a3: rawptr),
+ user_data : rawptr,
+ allocator : Allocator,
+ logger : Logger,
+}
+
diff --git a/thirdparty/sokol/build_clibs_linux.sh b/thirdparty/sokol/build_clibs_linux.sh
new file mode 100644
index 0000000..bad1701
--- /dev/null
+++ b/thirdparty/sokol/build_clibs_linux.sh
@@ -0,0 +1,49 @@
+set -e
+
+build_lib_x64_release() {
+ src=$1
+ dst=$2
+ backend=$3
+ echo $dst
+ # static
+ cc -pthread -c -O2 -DNDEBUG -DIMPL -D$backend c/$src.c
+ ar rcs $dst.a $src.o
+ # shared
+ cc -pthread -shared -O2 -fPIC -DNDEBUG -DIMPL -D$backend -o $dst.so c/$src.c
+}
+
+build_lib_x64_debug() {
+ src=$1
+ dst=$2
+ backend=$3
+ echo $dst
+ # static
+ cc -pthread -c -g -DIMPL -D$backend c/$src.c
+ ar rcs $dst.a $src.o
+ # shared
+ cc -pthread -shared -g -fPIC -DIMPL -D$backend -o $dst.so c/$src.c
+}
+
+# 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
+
+# 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_gl gl/sokol_gl_linux_x64_gl_debug SOKOL_GLCORE
+
+rm *.o
diff --git a/thirdparty/sokol/build_clibs_macos.sh b/thirdparty/sokol/build_clibs_macos.sh
new file mode 100644
index 0000000..c0743f5
--- /dev/null
+++ b/thirdparty/sokol/build_clibs_macos.sh
@@ -0,0 +1,127 @@
+set -e
+
+build_lib_arm64_release() {
+ src=$1
+ dst=$2
+ backend=$3
+ echo $dst
+ MACOSX_DEPLOYMENT_TARGET=10.13 clang -c -O2 -x objective-c -arch arm64 -DNDEBUG -DIMPL -D$backend c/$src.c
+ ar rcs $dst.a $src.o
+}
+
+build_lib_arm64_debug() {
+ src=$1
+ dst=$2
+ backend=$3
+ echo $dst
+ MACOSX_DEPLOYMENT_TARGET=10.13 clang -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 clang -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 clang -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
+
+# 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
+
+# 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
+
+# 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
+
+# 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
+
+rm *.o
diff --git a/thirdparty/sokol/build_clibs_macos_dylib.sh b/thirdparty/sokol/build_clibs_macos_dylib.sh
new file mode 100644
index 0000000..041ae45
--- /dev/null
+++ b/thirdparty/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 clang -c -O2 -x objective-c -arch $arch -DNDEBUG -DIMPL -D$backend c/$src.c
+ clang -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 clang -c -g -x objective-c -arch $arch -DIMPL -D$backend c/$src.c
+ clang -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_GLCORE arm64
+build_lib_debug sokol dylib/sokol_dylib_macos_arm64_gl_debug SOKOL_GLCORE arm64
+build_lib_release sokol dylib/sokol_dylib_macos_x64_gl_release SOKOL_GLCORE x86_64
+build_lib_debug sokol dylib/sokol_dylib_macos_x64_gl_debug SOKOL_GLCORE x86_64
+
+rm *.o
diff --git a/thirdparty/sokol/build_clibs_wasm.bat b/thirdparty/sokol/build_clibs_wasm.bat
new file mode 100644
index 0000000..4f50c61
--- /dev/null
+++ b/thirdparty/sokol/build_clibs_wasm.bat
@@ -0,0 +1,19 @@
+@echo off
+
+set sources=log app gfx glue time audio debugtext shape gl
+
+REM Debug
+for %%s in (%sources%) do (
+ echo %%s\sokol_%%s_wasm_gl_debug.a
+ call emcc -c -g -DIMPL -DSOKOL_GLES3 c\sokol_%%s.c
+ call emar rcs %%s\sokol_%%s_wasm_gl_debug.a sokol_%%s.o
+ del sokol_%%s.o
+)
+
+REM Release
+for %%s in (%sources%) do (
+ echo %%s\sokol_%%s_wasm_gl_release.a
+ call emcc -c -O2 -DNDEBUG -DIMPL -DSOKOL_GLES3 c\sokol_%%s.c
+ call emar rcs %%s\sokol_%%s_wasm_gl_release.a sokol_%%s.o
+ del sokol_%%s.o
+)
diff --git a/thirdparty/sokol/build_clibs_wasm.sh b/thirdparty/sokol/build_clibs_wasm.sh
new file mode 100644
index 0000000..559ef11
--- /dev/null
+++ b/thirdparty/sokol/build_clibs_wasm.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+set -e
+
+declare -a libs=("log" "gfx" "app" "glue" "time" "audio" "debugtext" "shape" "gl")
+
+for l in "${libs[@]}"
+do
+ echo "${l}/sokol_${l}_wasm_gl_debug.a"
+ emcc -c -g -DIMPL -DSOKOL_GLES3 c/sokol_$l.c
+ emar rcs $l/sokol_${l}_wasm_gl_debug.a sokol_$l.o
+ rm sokol_$l.o
+done
+
+for l in "${libs[@]}"
+do
+ echo "${l}/sokol_${l}_wasm_gl_release.a"
+ emcc -c -O2 -DNDEBUG -DIMPL -DSOKOL_GLES3 c/sokol_$l.c
+ emar rcs $l/sokol_${l}_wasm_gl_release.a sokol_$l.o
+ rm sokol_$l.o
+done
diff --git a/thirdparty/sokol/build_clibs_windows.cmd b/thirdparty/sokol/build_clibs_windows.cmd
new file mode 100644
index 0000000..f81e79e
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/build_shaders_macos.sh b/thirdparty/sokol/build_shaders_macos.sh
new file mode 100644
index 0000000..dd12348
--- /dev/null
+++ b/thirdparty/sokol/build_shaders_macos.sh
@@ -0,0 +1,30 @@
+set -e
+
+sokol_tools_root=../sokol-tools-bin
+
+build_shader() {
+ name=$1
+ dir=examples/$name
+ if [[ $(arch) =~ "arm64" ]]
+ then
+ shdc=$sokol_tools_root/bin/osx_arm64/sokol-shdc
+ else
+ shdc=$sokol_tools_root/bin/osx/sokol-shdc
+ fi
+ echo $dir
+ $shdc -i $dir/shader.glsl -o $dir/shader.odin -l glsl430:metal_macos:hlsl5 -f sokol_odin
+}
+
+build_shader blend
+build_shader bufferoffsets
+build_shader cube
+build_shader instancing
+build_shader instancing-compute
+build_shader mrt
+build_shader noninterleaved
+build_shader offscreen
+build_shader quad
+build_shader shapes
+build_shader texcube
+build_shader triangle
+build_shader vertexpull
diff --git a/thirdparty/sokol/c/sokol.c b/thirdparty/sokol/c/sokol.c
new file mode 100644
index 0000000..8fe7ebb
--- /dev/null
+++ b/thirdparty/sokol/c/sokol.c
@@ -0,0 +1,16 @@
+#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_gl.h"
+#include "sokol_shape.h"
+#include "sokol_debugtext.h"
\ No newline at end of file
diff --git a/thirdparty/sokol/c/sokol_app.c b/thirdparty/sokol/c/sokol_app.c
new file mode 100644
index 0000000..8cf2b17
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_app.h b/thirdparty/sokol/c/sokol_app.h
new file mode 100644
index 0000000..c91b68b
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_audio.c b/thirdparty/sokol/c/sokol_audio.c
new file mode 100644
index 0000000..53cdb49
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_audio.h b/thirdparty/sokol/c/sokol_audio.h
new file mode 100644
index 0000000..cd220ce
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_debugtext.c b/thirdparty/sokol/c/sokol_debugtext.c
new file mode 100644
index 0000000..42ad569
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_debugtext.h b/thirdparty/sokol/c/sokol_debugtext.h
new file mode 100644
index 0000000..904b778
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_defines.h b/thirdparty/sokol/c/sokol_defines.h
new file mode 100644
index 0000000..41f0c95
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_gfx.c b/thirdparty/sokol/c/sokol_gfx.c
new file mode 100644
index 0000000..984b8b4
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_gfx.h b/thirdparty/sokol/c/sokol_gfx.h
new file mode 100644
index 0000000..1e63aa3
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_gl.c b/thirdparty/sokol/c/sokol_gl.c
new file mode 100644
index 0000000..f15e4a7
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_gl.h b/thirdparty/sokol/c/sokol_gl.h
new file mode 100644
index 0000000..b419b15
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_glue.c b/thirdparty/sokol/c/sokol_glue.c
new file mode 100644
index 0000000..33d23ce
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_glue.h b/thirdparty/sokol/c/sokol_glue.h
new file mode 100644
index 0000000..a715b17
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_log.c b/thirdparty/sokol/c/sokol_log.c
new file mode 100644
index 0000000..bfdc1da
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_log.h b/thirdparty/sokol/c/sokol_log.h
new file mode 100644
index 0000000..0eef3a9
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_shape.c b/thirdparty/sokol/c/sokol_shape.c
new file mode 100644
index 0000000..73449f0
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_shape.h b/thirdparty/sokol/c/sokol_shape.h
new file mode 100644
index 0000000..d1676dd
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_time.c b/thirdparty/sokol/c/sokol_time.c
new file mode 100644
index 0000000..335304c
--- /dev/null
+++ b/thirdparty/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/thirdparty/sokol/c/sokol_time.h b/thirdparty/sokol/c/sokol_time.h
new file mode 100644
index 0000000..fd766d8
--- /dev/null
+++ b/thirdparty/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